1

I am trying to wire up DI on angular2. I basically have all my services which I expect to be singletons in the providers array in my app.module.ts.

providers: [
    { provide: ErrorHandler, useClass: IonicErrorHandler },
    { provide: 'ICommentService', useClass: CommentService },
    { provide: 'IPostService', useClass: PostService },
    { provide: 'IQueryParamsService', useClass: QueryParamsService },
    { provide: 'IUserService', useClass: UserService },
    { provide: 'IInjectorService', useClass: InjectorService},
    { provide: 'IPostFactory', useClass: PostFactory}
]

I also want to have a ReflectiveInjector that will be in charge of creating instances of objects I need. I created it like this:

this.injector = ReflectiveInjector.resolveAndCreate([
   { provide: 'IComment', useClass: Comment },
   { provide: 'ICommentList', useClass: CommentList },
   { provide: 'IPic', useClass: Pic },
   { provide: 'IPost', useClass: Post },
   { provide: 'IUser', useClass: User }            
]);

My issue is that for example Post requires a PostService and when I ask this injector for a Post it errors out because it can't find the PostService, which makes sense because it's separate injector trees.

I ask the injector like this:

this.injector.get('IPost');

This is part of my Post class:

@Injectable()
export class Post extends DBObject implements IPost{
    private title: string;
    private body:string;
    //private comments:ICommentList;
    //private img:IPic;
    //private author:IUser;
    private authorId: number;
    private date:Date;

    constructor(
        @Inject('IPostService') private PostService:IPostService,
        @Inject('IUserService') private UserService:IUserService,
        @Inject('IUser') private author:IUser,
        @Inject('IPic') private img: IPic,
        @Inject('ICommentList') private comments:ICommentList
    ){
        super();
    }

I omitted irrelevant functions. I expect IPostService, IUserService to be the singleton declared in the module but IUser, IPic and ICommentList should be new instances every time.

The error is:

Error in ./TabsPage class TabsPage - caused by: No provider for IPostService! (IPost -> IPostService)

How can I accomplish this?

Thanks!

PS: This is an angular problem but I'm using Ionic in case it matters.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
yafrack
  • 654
  • 1
  • 8
  • 24

1 Answers1

2

You probably want

constructor(injector:Injector) {
  resolvedProviders = ReflectiveInjector.resolve([
   { provide: 'IComment', useClass: Comment },
   { provide: 'ICommentList', useClass: CommentList },
   { provide: 'IPic', useClass: Pic },
   { provide: 'IPost', useClass: Post },
   { provide: 'IUser', useClass: User }            
  ]);
  let this.injector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, injector);

Multiple calls to

this.injector.get('IPost');

will get you the same Post instance though.

Angular2 DI maintains a single instance per provider. If you want to get a different instance for each call, you can provide a factory, get this factory from DI and call the factory to get a new instance. If you need more details on this approach leave a comment.

See also

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • This approach looks good. So I can use useClass for services and useFactory for objects that I want instantiated. Where does the injector in the constructor come from? – yafrack Dec 17 '16 at 19:13
  • Yes, use `useClass` to let Angulars DI create the instance for you. `useFactory` by itself doesn't create new instances, it just allows to customize how DI should create the one instance. To get multiple instances, you need to inject a function that, when called, creates a new instance, and then you call it once for each new instance you need. – Günter Zöchbauer Dec 17 '16 at 19:16
  • Yes so I have the same as in the link you share (return new Object()) in the factories and that should do. I did put this in an injector service so i can do comething like InjectorService.getInjector().get('IPost) from everywhere. This service is in app.module.ts (in the main post). I'm not sure where the injector in your constructor is coming from or how to pass it in. – yafrack Dec 17 '16 at 19:27
  • Sorry, forgot to respond to that point. The injector is an injector Angular uses. What exact injector it is, depends on where the constructor is. If it's a constructor of a service, then it's the root injector that is passed (if the service is provided in non-lazy loaded modules), if it's the constructor of a component or directive, then the injector of that component or directive is injected. The providers available in this injector are the providers of the component or directive or any of it's parent components or the root scope (provided in `@NgModule`). – Günter Zöchbauer Dec 17 '16 at 19:43
  • Are you implying it should be injected by Angular by default? I have a { provide: 'IInjectorService', useClass: InjectorService} in the providers array of app.module.ts and then the (injector:Injector) in the constructor of that service and => Can't resolve all parameters for InjectorService: (?). Note, this isn't a component or directive, just a normal class (service) – yafrack Dec 17 '16 at 19:53
  • Not sure I understand this comment. My suggestion (answer above) is to create a child injector of an Angular2 injector of your current app, where the injector knows all providers of the current scope **and** the providers you pass to `ReflectiveInjector.resolve(...)`. This way your custom injector can look up providers provided by `providers: [ ... ]` which I think is missing and therefore causes the error message you mention in your question. – Günter Zöchbauer Dec 17 '16 at 20:00
  • 1
    Yeah I probably didn't explain it corrently but I got it working. Thanks for your help ;) – yafrack Dec 17 '16 at 20:11
  • You're welcome. Glad to hear you could make it work :) – Günter Zöchbauer Dec 17 '16 at 20:13