I'm working with angular, jspm, and es6. I'm working with a base class to inject dependencies onto the constructor and automatically register themselves on 'this'.
This is actually a pattern that exists in React when you extend the base component class. I found this guy's little shortcut method here: http://www.newmediacampaigns.com/blog/refactoring-react-components-to-es6-classes
I am looking for a way to do this with Angular, using es6 Classes to bind the injected dependencies to the constructor's "this".
class baseClass {
constructor(...injections) {
this._bind(injections)
}
_bind(injections) {
injections.forEach( (injection) => {
this[injection.name] = injection;
});
}
}
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super($q, SomeAngularFactory);
}
}
This obviously doesn't work (injection.name is not a thing, i know)... but it almost does. My question is how do i get the "name" of the injected function or object. In this example, the _bind function just gives you the raw object or function... i don't know how to get "$q" or "SomeAngularFactory" as a string.
You can kind of get that by using "Object.getOwnPropertyNames(...injections)", but not inside the _bind function.
Thanks for any feedback or ideas you have.
You could do something like this:
class baseClass {
constructor(injections) {
Object.assign(this, injections);
}
}
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super({ $q, SomeAngularFactory });
}
}
Obs.: Like classes, Object.assign is an ES2015 feature and it merges the second (parameter) object into the first one.
In AngularJS this injectable arguments works only on development mode. If you go to production and minify your scripts, all injectables should be saved into special property. You can learn more about it in the angular docs. Here how it works with angular controllers
function MyController($q, SomeAngularFactory) {
}
MyController.$inject = ['$q', 'SomeAngularFactory']
Angular 2.0 will be powered by TypeScript and will use annotations to describe injectable variables. For now in anguar 1.3 you still can add static property $inject
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super($q, SomeAngularFactory);
}
}
DiClass.$inject = ['$q', 'SomeAngularFactory'];
Angular has a built in method, $injector.annotate(fn), which it uses for dependency injection, and which allows you to get arguments of the function you pass to it. You could utilize it to get the names of all constructor() parameters, and then use $injector.get() to retrieve them (which should be fine performance-wise, since they are cached).
Here's a link to answer in another thread, and a direct link to a fiddle with a demo.
Related
When I display my controller (class) in console, I see that all dependencies are visible and all the dependencies of my dependencies are visible by the scope ...
But I know that everthing should be private if I use functions.
My question is : What is the best way to create controllers in angularjs (1.6) and What is the difference between classes (ES6) and functions.
You can try the following pattern to keep those dependencies private and not have to assign them all to controller object.
function yourControllerFactory(yourDep1, yourDep2, ...) {
class YourController {
constructor() {
// no longer have to assign every dependency to this object
}
...
doSomething() {
yourDep1.doSomethingWithDep();
}
}
return new YourController();
}
module('your-module').controller('yourController', yourControllerFactory);
This way, instances of YourController will NOT have access to your injected dependencies. This way is similar to how controllers were defined in the past.
So I am used to using factories & services in Angular.
I am reading through the Angular2 docs and I don't see any equivalent of a factory. What is the equivalent for Angular2?
Factories, services, constants and values are all gone in Angular2. Angular2 is radically and fundamentally different from the classic Angular. In Angular2, the core concepts are
components
dependency injection
binding
The idea of services, factories, providers and constants has been criticized in Angular 1. It was difficult to choose between one. Removing them simplifies things a bit.
In the original Angular, you would define a service like so
app.service('BookService', ['$http', '$q', BookService]);
function BookService($http, $q){
var self = this;
var cachedBooks;
self.getBooks = function(){
if (cachedBooks) {
return $q.when(cachedBooks);
}
return $http.get('/books').then(function(response){
cachedBooks = response.data.books;
return cachedBooks;
})
}
}
Angular2 significantly leverages ES6 syntax to make the code more readable and easier to understand.
One new keyword in ES6 is class, which can be thought of as a service.
ES6 classes are a simple sugar over the prototype-based OO pattern. Having a single convenient declarative form makes class patterns easier to use, and encourages interoperability. Classes support prototype-based inheritance, super calls, instance and static methods and constructors.
Here's how that same code might look in Angular2
import {HttpService, Promise} from '../Angular/Angular2';
export class BookService{
$http, $q, cachedBooks;
constructor($http: HttpService, $q: Promise) {
this.$http = $http;
this.$q = $q
}
getBooks() {
if (this.cachedBooks) {
return this.$q.when(this.cachedBooks);
}
return this.$http.get('/books').then(function(data) {
this.cachedBooks = data.books;
return this.cachedBooks;
})
}
}
#Richard Hamilton's answer is appreciated and in addition to that there are few points to note.
For Factories,Service, and etc, in Angular2 we have service (or shared service). we have to make our service Injectable in order to use it.
NOTE: This code belongs to beta version and not RC.
import {Component, Injectable,Input,Output,EventEmitter} from 'angular2/core'
import {Router} from 'angular2/router';
import {Http} from 'angular2/http';
export interface ImyInterface {
show:boolean;
}
#Injectable() <---------------------------- Very Important
export class sharedService { <----------------- Service Name
showhide:ImyInterface={show:true};
constructor(http:Http;router:Router)
{
this.http=http;
}
change(){
this.showhide.show=!this.showhide.show;
}
}
If I want to use everywhere in my app, then I have to inject this service in bootstrap function like this,
bootstrap(App, [HTTP_PROVIDERS,sharedService <--------Name Injection
ROUTER_PROVIDERS,bind(APP_BASE_HREF).toValue(location.pathname)
]);
This way it creates single instance of your service. If you don't want to go with single instance, what you can do is - you can use Providers:[sharedService] metadata in you #component decorator.
Then, use it in your one of components like this,
export class TheContent {
constructor(private ss: sharedService) { <--------Injection dependency of your newly created service
console.log("content started");
}
showhide() {
this.ss.change(); <----- usage
}
}
Check working example here
I don't know what factories do exactly in Angular1 but in Angular2 there is useFactory:
{
provide: SomeClass,
useFactory: (dep1, dep2) => (x) => new SomeClassImpl(x, dep1, dep2),
deps: [Dep1, Dep2]
}
to provide your own instance construction logic if the default doesn't match your needs.
You can also inject a factory to create new instances yourself:
/* deprecated or removed depending on the Angular version you are using */
provide(SomeClass, {
useFactory: (dep1, dep2) => {
(x) => new SomeClassImpl(x, dep1, dep2),
},
deps: [Dep1, Dep2]
})
constructor(#Inject(SomeClass) someClassFactory: any) {
let newSomeClass = someClassFactory(1);
}
Argument x must have type assignment, otherwise angular doesn't know how to deal with it.
class SomeClassImpl {
constructor(x: number, dep1: Dep1, dep2: Dep2){}
}
If you need a new instance of a service in some component you need to provide it in that component like this:
#Component({
selector: 'hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ]
})
This will generate a new instance of the HereService as a factory does.
I'm constantly failing to inject the $log service in a minify save way into a controller class of a component.
To check the injection is save, I added ng-strict-di to my app. This in terms causes an SearchResultController is not using explicit annotation and cannot be invoked in strict mode which is fine, since I'm relying on implicit injection right now.
So I added an explicit injection to my component implementation:
import htmlTemplate from './searchInput.html';
class SearchInputController {
constructor($log) {
this._log = $log;
this.searchText = 'Text to search for';
}
handleUpdate() {
this.onChange({value: this.searchText});
}
doubleMe(i) {
this._log.debug('SearchInputController.doubleMe: i='+i);
return i+i;
}
}
SearchInputController.$inject = ['$log'];
let searchInputComponent = {
template: htmlTemplate,
controller: SearchInputController,
bindings: {
onChange: '&'
}
};
export default searchInputComponent;
This has no effect the error message is still complaining about the missing injection.
I also tried ng-annotate-loader and ng-annotate-webpack-plugin. And also tried the /*#ngInject*/ and 'ngInject'; type annotations. All of this has no effect.
Any ideas how to get the dependency injection working?
Your annotation appears to be correct for SearchInputController, but the error message is complaining about a different controller SearchResultController. I suggest you need to annotate that one also.
Use https://babeljs.io/repl/ to check how ES6 compiles down to ES5. This can be useful when trying to trace errors in annotation. Using ng-annotate in some form should also work when you annotate the correct controller.
I'm building an application with angular and recently switched to TypeScript. The core of my application consists of multiple actual classes that interact with eachother (so not just global services). The way I did this is as follows:
class Person {
constructor(private $http, public name: string) {
// ... use this.$http for some task
}
}
class PersonFactory {
static $inject = ['$http'];
constructor(private $http) {}
create(name: string) {
return new Person(this.$http, name);
}
}
angular.module('app').service('personFactory', PersonFactory);
So if some other class needs to create a Person object, it needs to get injected with PersonFactory and call its create method. This works ok, even though it requires some boilerplate.
My main problem arises when I want to subclass Person:
class Student extends Person {
constructor(private $http) {
super(this.$http, 'Some actual name')
}
}
I could follow the same pattern as above, but then I need to, again, create a StudentFactory that passes the required services like $http to the class constructor, which in turn passes it to the constructor of the super method. If I now had multiple such extending classes and would change the base class Person, I had to change every extending class and their factories. Is there a better way to approach this problem? Might it be better to just remove all dependencies on angular services in my core application and just use angular for the UI code?
Solution
As suggested in the answers, I added the $injector service to the window, like this:
module('app').run(['$injector', function($injector) {
window.$injector = $injector;
}]);
The service can now be used in any class to get other services like $http.
This didn't work for unit tests though, so I had to put the injector on the global scope again:
// MyClass.test.js
var $injector;
describe('My class', function() {
beforeEach(module('app'));
beforeEach(inject(function(_$injector_) {
$injector = _$injector_;
}));
...
});
I had to change every extending class and their factories. Is there a better way to approach this problem
Just inject $injector and then the base class can get what it wants using $injector.get().
or even more crazy ... put $injector (its a singleton) on the global scope / or some module ... and just use that in your classes to get angular specific stuff.
In this the repo
In the controller, I'm trying to inject the UserService:
class UserDetailController implements IUserDetailScope {
static $inject = ['app.core.services.UserService']; // static injection
constructor(userService: app.core.services.IUserService) {
But it fails on the browser' console with:
Unknown provider: app.core.services.UserServiceProvider <-
app.core.services.UserService <- UserDetailController
Could you say why?
Try upper casing the "U" in UserService in the constructor. When angular goes to inject, it just does a match on the name of the service, and it matches case.
EDIT
Just realized you're using $inject, I'm leaving the first response up in case some others find it.
You didn't show us the code that registers the service, but that's the next culprit, usually. I'd guess you're registering it as 'UserService', not 'app.core.services.UserService', or else you're doing an App.controller() instead of App.service() registration. Those are the other two major culprits that come to mind.
you are writing the whole namespace to inject a service but there is neat way in which i do
module portal {
var app =angular.module('fooModule',[]);
app.service(services);
app.controller(controllers);
}
module portal.services {
//all your services will go under this namespace
export class fooService{
//service body here
}
}
module portal.controller{
export class UserDetailController implements IUserDetailScope {
static $inject = ['UserService'];
constructor(userService: potal.services.IUserService) {
}
}