In Angular 1, there was a broad selection of service types you could use in your application. A subset of these types allowed you to inject a static value instead of a service instance, and this useful ability is continued in Angular 2.
The code, links, and a live example of this are available at .
Begin with the following simple application:
[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'; @NgModule({ imports: [ BrowserModule ], declarations: [ RootComponent, ArticleComponent ], bootstrap: [ RootComponent ] }) export class AppModule {} [app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <article></article> ` }) export class RootComponent {} [app/article.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'article', template: ` <img src="{{logoUrl}}"> <h2>Fool and His Money Reunited at Last</h2> <p>Author: Jake Hsu</p> ` }) export class ArticleComponent {}
Although a formal service class declaration and @Injectable
decorator designation is no longer necessary for injecting a value, token/provider mapping is still needed. Since there is no longer a class available that can be used to type the injectable, something else will have to act as its replacement.
Angular 2 solves this problem with OpaqueToken
. This module allows you to create a classless token that can be used to pair the injected value with the constructor argument. This can be used alongside the useValue
provide option, which simply directly provides whatever its contents are as injected values.
Define a token using a unique string in its constructor:
[app/logo-url.token.ts] import {OpaqueToken} from '@angular/core'; export const LOGO_URL = new OpaqueToken('logo.url');
Incorporate this token into the application module definition as you normally would. However, you must specify what it will actually point to when it is injected. In this case, it should resolve to an image URL string:
[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 {LOGO_URL} from './logo-url.token'; @NgModule({ imports: [ BrowserModule ], declarations: [ RootComponent, ArticleComponent ], providers: [ {provide: LOGO_URL, useValue: 'https://angular.io/resources/images/logos/standard/logo-nav.png'} ], bootstrap: [ RootComponent ] }) export class AppModule {}
Finally, you'll be able to inject this into a component. However, since you're injecting something that wasn't defined with the @Injectable()
decoration, you'll need to use @Inject()
inside the constructor to tell Angular that it should be provided, using dependency injection. Furthermore, the injection will not attach itself to the component's this
, so you'll need to do this manually as well:
[app/article.component.ts] import {Component, Inject} from '@angular/core'; import {LOGO_URL} from './logo-url.token'; @Component({ selector: 'article', template: ` <img src="{{logoUrl}}"> <h2>Fool and His Money Reunited at Last</h2> <p>Author: Jake Hsu</p> ` }) export class ArticleComponent { logoUrl:string; constructor(@Inject(LOGO_URL) private logoUrl_) { this.logoUrl = logoUrl_; } }
With this, you should be able to see the image rendered in your browser!
OpaqueToken
allows you to use non-class types inside Angular 2's class-centric provider schema. It generates a simple class instance that essentially is just a wrapper for the custom string you provided. This class is what the dependency injection framework will use when attempting to map injection tokens to provider declarations. This gives you the ability to more widely utilize dependency injection throughout your application since you can now feed any type of value wherever a service type can be injected.
One other way in which injecting values is useful is that it gives you the ability to stub out services. Suppose you wanted to define a default stub service that should be overridden with an explicit provider to enable useful behavior. In such a case, you can imagine a default article entity that could be differently configured via a directive while reusing the same component:
[app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <article></article> <article editor-view></article> ` }) export class RootComponent {} [app/editor-article.service.ts] import {Injectable} from '@angular/core'; export const MockEditorArticleService = { getArticle: () => ({ title: "Mock title", body: "Mock body" }) }; @Injectable() export class EditorArticleService { private title_:string = "Prominent Vegan Embroiled in Scrambled Eggs Scandal"; private body_:string = "Tofu Farming Alliance retracted their endorsement."; getArticle() { return { title: this.title_, body: this.body_ }; } } [app/editor-view.directive.ts] import {Directive} from '@angular/core'; import {EditorArticleService} from './editor-article.service'; @Directive({ selector: '[editor-view]', providers: [EditorArticleService] }) export class EditorViewDirective {} [app/article.component.ts] import {Component, Inject} from '@angular/core'; import {EditorArticleService} from './editor-article.service'; @Component({ selector: 'article', template: ` <h2>{{title}}</h2> <p>{{body}}</p> ` }) export class ArticleComponent { title:string; body:string; constructor(private editorArticleService_:EditorArticleService) { let article = editorArticleService_.getArticle(); this.title = article.title; this.body = article.body; } }
With this, your ArticleComponent
, as defined in the preceding code, would use the mock service when the directive is not attached and the actual service when it is attached.