As your application becomes more complex, you may come to a situation where you would like to use your services in a polymorphic style. More specifically, some places in your application may want to request Service A, but a configuration somewhere in your application will actually give it Service B. This recipe will demonstrate one way in which this can be useful, but this behavior allows your application to be more extensible in multiple ways.
The code, links, and a live example of this are available at .
Suppose you begin with the following skeleton application.
You begin with two services, ArticleService
and EditorArticleService
, and their shared interface, ArticleSourceInterface
. EditorArticleService
inherits from ArticleService
:
[app/article-source.interface.ts] export interface ArticleSourceInterface { getArticle():Article } export interface Article { title:string, body:string, // ? denotes an optional property notes?:string } [app/article.service.ts] import {Injectable} from '@angular/core'; import {Article, ArticleSourceInterface} from './article-source.interface'; @Injectable() export class ArticleService implements ArticleSourceInterface { private title_:string = "Researchers Determine Ham Sandwich Not Turing Complete"; private body_:string = "Computer science community remains skeptical"; getArticle():Article { return { title: this.title_, body: this.body_ }; } } [app/editor-article.service.ts] import {Injectable} from '@angular/core'; import {ArticleService} from './article.service'; import {Article, ArticleSourceInterface} from './article-source.interface'; @Injectable() export class EditorArticleService extends ArticleService implements ArticleSourceInterface { private notes_:string = "Swing and a miss!"; constructor() { super(); } getArticle():Article { // Combine objects and return the joined object return Object.assign( {}, super.getArticle(), { notes: this.notes_ }); } }
Your objective is to be able to use the following component so that both these services can be injected into the following component:
[app/article.component.ts] import {Component} from '@angular/core'; import {ArticleService} from './article.service'; import {Article} from './article-source.interface'; @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:Article; constructor(private articleService_:ArticleService) { this.article = articleService.getArticle(); } }
When listing providers, Angular 2 allows you to declare an aliased reference that specifies what service should actually be provided when one of the certain types is requested. Since Angular 2 injection will follow the component tree upwards to find the provider, one way to declare this alias is by wrapping the component with a parent component that will specify this alias:
[app/default-view.component.ts] import {Component} from '@angular/core'; import {ArticleService} from './article.service'; @Component({ selector: 'default-view', template: ` <h3>Default view</h3> <ng-content></ng-content> `, providers: [ArticleService] }) export class DefaultViewComponent {} [app/editor-view.component.ts] import {Component } from '@angular/core'; import {ArticleService} from './article.service'; import {EditorArticleService} from './editor-article.service'; @Component({ selector: 'editor-view', template: ` <h3>Editor view</h3> <ng-content></ng-content> `, providers: [ {provide: ArticleService, useClass: EditorArticleService} ] }) export class EditorViewComponent {}
Note that both these classes are acting as passthrough components. Other than adding a header (which is merely for learning the purpose of instruction in this recipe), these classes are only specifying a provider and are unconcerned with their content.
With the wrapper classes defined, you can now add them to the application module, then use them to create two instances of ArticleComponent
:
[app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {RootComponent} from './root.component'; import {ArticleComponent} from './article.component'; import {DefaultViewComponent} from './default-view.component'; import {EditorViewComponent} from './editor-view.component'; import {ArticleComponent} from './article.component'; import {ArticleService} from './article.service'; import {EditorArticleService} from './editor-article.service'; @NgModule({ imports: [ BrowserModule ], declarations: [ RootComponent, ArticleComponent, DefaultViewComponent, EditorViewComponent ], bootstrap: [ RootComponent ] }) export class AppModule {} [app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <default-view> <article></article> </default-view> <hr /> <editor-view> <article></article> </editor-view> ` }) export class RootComponent {}
With this, you should now see that the editor version of ArticleComponent
gets the notes, but the default version does not.
In Angular 1, the service type that was supposed to be injected was identified from a function parameter by doing a direct match of the parameter symbol. function(Article)
would inject the Article
service, function (User)
the User
service, and so on. This led to nastiness, such as the minification-proofing of constructors by providing an array of strings to match against ['Article', function(Article) {}]
.
This is no longer the case. When a provider is registered, the useClass
option utilizes the two-part dependency injection matching scheme in Angular 2. The first part is the provider token, which is the parameter type of the service being injected. In this case, private articleService_:ArticleService
uses the ArticleService
token to request that an instance be injected. Angular 2 takes this and matches this token against the declared providers
in the component hierarchy. When a token is matched, Angular 2 will use the second part, the provider
itself, to inject an instance of the service.
In reality, providers: [ArticleService]
is a shorthand for providers: [{provide: ArticleService, useClass: ArticleService}]
. The shorthand is useful since you will almost always be requesting the service class that would match the injected class. However, in this recipe, you are configuring Angular 2 to recognize an ArticleService
token and so use the EditorArticleService
provider.
An attentive developer will have realized by this point that the utility of useClass
is limited in the sense that it does not allow you to independently control where the actual service is provided. In other words, the place where you intercept the provider definition with useClass
is also the place where the replacement class will be provided.
In this example, useClass
is suitable since you are perfectly happy to provide EditorArticleService
in the same place where you are specifying that it should be used to replace ArticleService
. However, it is not difficult to imagine a scenario in which you would like to specify the replacement service type but have it injected higher up in the component tree. This, after all, would allow you to reuse instances of a service instead of having to create a new one for each useClass
declaration.
For this purpose, you can use useExisting
. It requires you to explicitly provide the service type separately, but it will reuse the provided instance instead of creating a new one. For the application you just created, you can now reconfigure it with useExisting
, and provide both the services at the RootComponent
level.
To demonstrate that your reasoning about the service behavior is correct, double the number of Article
components, and add a log statement to the constructor of ArticleService
to ensure you are only creating one of each service:
[app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <default-view> <article></article> </default-view> <editor-view> <article></article> </editor-view> <default-view> <article></article> </default-view> <editor-view> <article></article> </editor-view> ` }) export class RootComponent {} [app/article.service.ts] import {Injectable} from '@angular/core'; import {Article, ArticleSourceInterface} from './article-source.interface'; @Injectable() export class ArticleService implements ArticleSourceInterface { private title_:string = "Researchers Determine Ham Sandwich Not Turing Complete"; private body_:string = "Computer science community remains skeptical"; constructor() { console.log('Instantiated ArticleService!'); } getArticle():Article { return { title: this.title_, body: this.body_ }; } } [app/editor-view.component.ts] import {Component} from '@angular/core'; import {ArticleService} from './article.service'; import {EditorArticleService} from './editor-article.service'; @Component({ selector: 'editor-view', template: ` <h3>Editor view</h3> <ng-content></ng-content> `, providers: [ {provide: ArticleService, useExisting: EditorArticleService} ] }) export class EditorViewComponent {} [app/default-view.component.ts] import {Component} from '@angular/core'; import {ArticleService} from './article.service'; @Component({ selector: 'default-view', template: ` <h3>Default view</h3> <ng-content></ng-content> ` // providers removed }) export class DefaultViewComponent {}
In this configuration, with useClass
, you will see that one instance of ArticleService
and two instances of EditorArticleService
are created. When replaced with useExisting
, you will find that only one instance of each is created.
Thus, in this reconfigured version of the recipe, your application is doing the following:
RootComponent
level, it is providing EditorArticleService
EditorViewComponent
level, it is redirecting ArticleService
injection tokens to EditorArticleService
ArticleComponent
level, it is injecting ArticleService
using the ArticleService
tokenIf this implementation seems clunky and verbose to you, you are certainly on to something. The intermediate components are performing their jobs quite well, but aren't really doing anything other than shimming in an intermediate provider's declaration. Instead of wrapping in a component, you can migrate the provider's statement into a directive and do away with both the view components:
[app/root.component.ts] import {Component} from '@angular/core'; import {ArticleComponent} from './ article.component'; import {ArticleService} from './article.service'; import {EditorArticleService} from './editor-article.service'; @Component({ selector: 'root', template: ` <article></article> <article editor-view></article> <article></article> <article editor-view></article> ` }) export class RootComponent {} [app/editor-view.directive.ts] import {Directive } from '@angular/core'; import {ArticleService} from './article.service'; import {EditorArticleService} from './editor-article.service'; @Directive({ selector: '[editor-view]', providers: [ {provide: ArticleService, useExisting: EditorArticleService} ] }) export class EditorViewDirective {} [app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {RootComponent} from './root.component'; import {ArticleComponent} from './article.component'; import {DefaultViewComponent} from './default-view.component'; import {EditorViewDirective} from './editor-view.directive'; import {ArticleComponent} from './article.component'; import {ArticleService} from './article.service'; import {EditorArticleService} from './editor-article.service'; @NgModule({ imports: [ BrowserModule ], declarations: [ RootComponent, ArticleComponent, DefaultViewComponent, EditorViewDirective ], providers: [ ArticleService, EditorArticleService ], bootstrap: [ RootComponent ] }) export class AppModule {}
Your application should work just the same!