One further extension of dependency injection in Angular 2 is the ability to use factories when defining your provider hierarchy. A provider factory allows you to accept input, perform arbitrary operations to configure the provider, and return that provider instance for injection.
The code, links, and a live example of this are available at .
Begin again with the dual service and article component setup shown in Service injection aliasing with useClass and useExisting, earlier in the chapter.
Provider factories in Angular 2 are exactly as you might imagine they would be: functions that return a provider. The factory can be specified in a separate file and referenced with the useFactory provide
option.
Begin by combining the two services into a single service, which will be configured with a method call:
[app/article.service.ts] import {Injectable} from '@angular/core'; @Injectable() export class ArticleService { private title_:string = "Flying Spaghetti Monster Sighted"; private body_:string = "Adherents insist we are missing the point"; private notes_:string = "Spot on!"; private editorEnabled_:boolean = false; getArticle():Object { var article = { title: this.title_, body: this.body_ }; if (this.editorEnabled_) { Object.assign(article, article, { notes: this.notes_ }); } return article; } enableEditor():void { this.editorEnabled_ = true; } }
Your objective is to configure this service to have enableEditor()
invoked based on a boolean
flag. With provider factories, this is possible. Define the factory in its own file:
[app/article.factory.ts] import {ArticleService} from './article.service'; export function articleFactory(enableEditor?:boolean):ArticleService { return (articleService:ArticleService) => { if (enableEditor) { articleService.enableEditor(); } return articleService; } }
Splendid! Next, you'll need to reconfigure ArticleComponent
to inject a token rather than the desired service:
[app/article.token.ts] import {OpaqueToken} from '@angular/core'; export const ArticleToken = new OpaqueToken('app.article'); [app/article.component.ts] import {Component, Inject} from '@angular/core'; import {ArticleToken} from './article.token'; @Component({ selector: 'article', template: ` <h2>{{article.title}}</h2> <p>{{article.body}}</p> <p *ngIf="article.notes"> <i>Notes: {{article.notes}}</i> </p> ` }) export class ArticleComponent { article:Object; constructor(@Inject(ArticleToken) private articleService_) { this.article = articleService_.getArticle(); } }
Finally, you'll need to define the directives that specify how to use this factory and incorporate them into the application:
[app/default-view.directive.ts] import {Directive} from '@angular/core'; import {ArticleService} from './article.service'; import {articleFactory} from './article.factory'; import {ArticleToken} from './article.token'; @Directive({ selector: '[default-view]', providers: [ {provide: ArticleToken, useFactory: articleFactory(), deps: [ArticleService] } ] }) export class DefaultViewDirective {} [app/editor-view.directive.ts] import {Directive} from '@angular/core'; import {ArticleService} from './article.service'; import {articleFactory} from './article.factory'; import {ArticleToken} from './article.token'; @Directive({ selector: '[editor-view]', providers: [ { provide: ArticleToken, useFactory: articleFactory(true), deps: [ArticleService] } ] }) export class EditorViewDirective {} [app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <article default-view></article> <article editor-view></article> ` }) export class RootComponent {}
With this, you should be able to see both the versions of ArticleComponent
.
The article component is redefined to use a token instead of a service injection. With the token, Angular will walk up the component tree to find where that token is provided. The directives declare that the token is mapped to a provider factory, which is a method invoked to return the actual provider.
useFactory
is the property that maps to the factory function. deps
is the property that maps to the service dependencies that the factory has.
An important distinction at this point is to recognize that all these factory configurations are happening before any components are instantiated. The class decoration that defines the providers will invoke the factory function on setup.