I have had mixed success with resolving issues with DI. I have read a few tutorials and I get the jist but after making some nested DIs with my custom services things have started to fall apart.
Could someone explain when to use useFactory instead of useClass? I have seen the ng2 docs and I have seen the examples but I cannot map them to my problems. Currently my bootstrapper looks like this:
bootstrap(
App,
[
FORM_PROVIDERS,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: PathLocationStrategy }),
provide(RequestOptions, { useClass: DefaultRequestOptions }),
provide(MsgService, { useClass: MsgService }),
provide(HttpAdvanced, { useFactory: (MsgService, HTTP_PROVIDERS) => new HttpAdvanced(MsgService, HTTP_PROVIDERS), deps: [MsgService, HTTP_PROVIDERS] }),
provide(AuthService, { useFactory: (HttpAdvanced) => new AuthService(HttpAdvanced), deps: [HttpAdvanced, HTTP_PROVIDERS, MsgService] }),
provide(FormBuilderAdvanced, { useFactory: (FormBuilder, HttpAdvanced) => new FormBuilderAdvanced(FormBuilder, HttpAdvanced), deps: [FormBuilder, HttpAdvanced] }),
provide(MsgServiceInternal, { useClass: MsgServiceInternal })
]
);
And my latest issue is:
EXCEPTION: Error during instantiation of AuthService! (HeaderBar -> AuthService).
ORIGINAL EXCEPTION: TypeError: this.http.get is not a function
My dependencies work like
HttpAdvanced -> Http(ng2), MsgService
MsgService -> MsgServiceInternal
AuthService -> HttpAdvanced
FormBuilderAdvanced -> FormBuilder(ng2), HttpAdvanced
1. Am I properly using provide / useClass / useFactory and how do I provide services that have other dependencies?
Also, I have at one place in my code:
static isUserInjector() {
return (next, prev) => Injector.resolveAndCreate([AuthService, provide(HttpAdvanced, { useClass: HttpAdvanced })]).get(AuthService).isUser();
}
Because I want to have a function that I provide to
@CanActivate(AuthService.isEditorInjector())
but I can't use constructor injection because @CanActivate is outside of the class scope so I can't inject the service inside the controller and then reference like @CanActivate(this.authService.isEditor())
2. What would be a good solution for this?
SOME CODE:
@Component({
selector: 'ShowStats',
templateUrl: './dest/views/showStats/showStats.html',
directives: [ COMMON_DIRECTIVES, UsersCount, AdminsList, GlobalWishlist, PopularTrack ]
})
export class ShowStats {
authService : AuthService;
constructor( authService : AuthService ){
this.authService = authService;
}
}
... next file ...
@Injectable()
export class HttpAdvanced {
msgService: MsgService;
http: Http;
constructor(msgService: MsgService, http: Http) {
this.msgService = msgService;
this.http = http;
}
/*
* This is for plain ol' GET requests .. with callback of course.
*/
public get(url, callback) {
return this.http.get(url).subscribe((res) => {
let data = res.json().data;
callback(data);
}, this.msgService.httpErrorHandler);
}
.... other code for HttpAdvanced
3.
Does the order of importing files matter for DI? I think i noticed that since I have MsgService and MsgServiceInternal in the same file and MsgService depends on Internal that I had to put Internal before but I'm not 100% Is it the same of order of importing
?
4. So if I simply do:
bootstrap(
App,
[
FORM_PROVIDERS,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: PathLocationStrategy }),
provide(RequestOptions, { useClass: DefaultRequestOptions }),
MsgService,
HttpAdvanced,
AuthService,
FormBuilderAdvanced,
MsgServiceInternal
]
);
I get:
Cannot resolve all parameters for 'FormBuilderAdvanced'(?, ?).
Make sure that all the parameters are decorated with Inject or have
valid type annotations and that 'FormBuilderAdvanced' is decorated
with Injectable.
I used to remove this error with the useFactory
but I'm confused now. Does this mean the deps aren't injected because it can't see them or what?
The Form class:
export class FormBuilderAdvanced {
http: HttpAdvanced;
fb: FormBuilder;
constructor(fb: FormBuilder, http: HttpAdvanced) {
this.fb = fb;
this.http = http;
}
create(controlNames: string[], submissionUrl: string, getter?: any) {
return new Form(this.fb, this.http, controlNames, submissionUrl, getter);
}
}
You question doesn't provide enough information to know for sure but this is probably enough
bootstrap(
App,
[
FORM_PROVIDERS,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: PathLocationStrategy }),
provide(RequestOptions, { useClass: DefaultRequestOptions }),
// provide(MsgService, { useClass: MsgService }),
MsgService, // is just fine when no special behavior is required
// provide(HttpAdvanced, { useFactory: (MsgService, HTTP_PROVIDERS) => new HttpAdvanced(MsgService, HTTP_PROVIDERS), deps: [MsgService, HTTP_PROVIDERS] }),
provide(Http, {useClass: HttpAdvanced});
AuthService,
provide(FormBuilder, { useClass: FormBuilderAdvanced}),
MsgServiceInternal)
]
);
If you want to make a class available for injection, just add the type to the providers list (provide(AuthService)
and just AuthService
do the same).
If you want inject a different class than the one requested, use useClass
.
Example
@Injectable()
export class MyService {
constructor(private http: Http) {
}
}
If your providers contain
provide(Http, {useClass: HttpAdvanced});
then MyService
(which has a dependency on Http
) gets injected HttpAdvanced
instead.
Ensure you have this line after HTTP_PROVIDERS
to override the default Http
provider, contained in HTTP_PROVIDERS
.
If DI can't resolve dependencies by itself because they aren't just other providers, then use useFactory
.
As mentioned above,
Ensure you have this line after
HTTP_PROVIDERS
to override the defaultHttp
provider, contained inHTTP_PROVIDERS
.
the order in the list of providers does matter. When the providers list contains multiple providers for a type then only the last one is used.
The order of the imports doesn't matter.
The order of dependent classes within one file does matter because classes are not hoisted.
Example
export class ClassA {
// constructor(private b: ClassB) {} // ClassB is unknown here
constructor(@Inject(forwardRef(() => DataService)) {
}
}
export class ClassB {
}
If the code of ClassB
is above ClassA
, forwardRef()
isn't necessary.
See also Class is not injectable if it is defined right after a component with meta annotation
To complete a bit the Günter's great and clear answer, I would say that I would use useFactory
to instantiate by myself classes associated with providers in the following use cases:
When you want to select the element to inject into the contructor of the element associated with the provider. For example, if you want to extend the HTTP
class and provide explicitly a specific concrete class for parameters (here XHRBackend
instead of ConnectionBackend
). Here is a sample:
bootstrap(AppComponent, [HTTP_PROVIDERS, ROUTER_PROVIDERS,
new Provider(Http, {
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => new CustomHttp(backend, defaultOptions),
deps: [XHRBackend, RequestOptions]
})
]);
@Langley provides interesting hints in this question: Handling 401s globally with Angular.
Another cool usage of the useFactory
is to instantiate a class from a third-party library into the context of Angular2 (Zones)
bootstrap(App, [
provide(Mousetrap, { useFactory: () => new Mousetrap() })
]);
@alexpods provides this very elegant solution in this question: View is not updated on change in Angular2.
Hope it helps you, Thierry
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With