docs(di-cookbook): InjectionToken->OpaqueToken; clarify class-interface

This commit is contained in:
Ward Bell 2017-04-01 00:08:46 -07:00
parent f38620d882
commit ebd8b48da6
11 changed files with 170 additions and 127 deletions

View File

@ -4,29 +4,10 @@ import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
// #docregion minimal-logger
// class used as a restricting interface (hides other public members)
export abstract class MinimalLogger {
logInfo: (msg: string) => void;
logs: string[];
}
// #enddocregion minimal-logger
/*
// Transpiles to:
// #docregion minimal-logger-transpiled
var MinimalLogger = (function () {
function MinimalLogger() {}
return MinimalLogger;
}());
exports("MinimalLogger", MinimalLogger);
// #enddocregion minimal-logger-transpiled
*/
// #docregion date-logger-service // #docregion date-logger-service
@Injectable() @Injectable()
// #docregion date-logger-service-signature // #docregion date-logger-service-signature
export class DateLoggerService extends LoggerService implements MinimalLogger export class DateLoggerService extends LoggerService
// #enddocregion date-logger-service-signature // #enddocregion date-logger-service-signature
{ {
logInfo(msg: any) { super.logInfo(stamp(msg)); } logInfo(msg: any) { super.logInfo(stamp(msg)); }

View File

@ -0,0 +1,26 @@
// Illustrative (not used), mini-version of the actual HeroOfTheMonthComponent
// Injecting with the MinimalLogger "interface-class"
import { Component, NgModule } from '@angular/core';
import { LoggerService } from './logger.service';
import { MinimalLogger } from './minimal-logger.service';
// #docregion
@Component({
selector: 'hero-of-the-month',
templateUrl: './hero-of-the-month.component.html',
// Todo: move this aliasing, `useExisting` provider to the AppModule
providers: [{ provide: MinimalLogger, useExisting: LoggerService }]
})
export class HeroOfTheMonthComponent {
logs: string[] = [];
constructor(logger: MinimalLogger) {
logger.logInfo('starting up');
}
}
// #enddocregion
// This NgModule exists only to avoid the Angular language service's "undeclared component" error
@NgModule({
declarations: [ HeroOfTheMonthComponent ]
})
class NoopModule {}

View File

@ -0,0 +1,9 @@
<h3>{{title}}</h3>
<div>Winner: <strong>{{heroOfTheMonth.name}}</strong></div>
<div>Reason for award: <strong>{{heroOfTheMonth.description}}</strong></div>
<div>Runners-up: <strong id="rups1">{{runnersUp}}</strong></div>
<p>Logs:</p>
<div id="logs">
<div *ngFor="let log of logs">{{log}}</div>
</div>

View File

@ -1,19 +1,19 @@
/* tslint:disable:one-line:check-open-brace*/ /* tslint:disable:one-line:check-open-brace*/
// #docplaster // #docplaster
// #docregion opaque-token // #docregion injection-token
import { OpaqueToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
export const TITLE = new OpaqueToken('title'); export const TITLE = new InjectionToken<string>('title');
// #enddocregion opaque-token // #enddocregion injection-token
// #docregion hero-of-the-month // #docregion hero-of-the-month
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { DateLoggerService, import { DateLoggerService } from './date-logger.service';
MinimalLogger } from './date-logger.service';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
import { MinimalLogger } from './minimal-logger.service';
import { RUNNERS_UP, import { RUNNERS_UP,
runnersUpFactory } from './runners-up'; runnersUpFactory } from './runners-up';
@ -22,28 +22,16 @@ import { RUNNERS_UP,
const someHero = new Hero(42, 'Magma', 'Had a great month!', '555-555-5555'); const someHero = new Hero(42, 'Magma', 'Had a great month!', '555-555-5555');
// #enddocregion some-hero // #enddocregion some-hero
const template = `
<h3>{{title}}</h3>
<div>Winner: <strong>{{heroOfTheMonth.name}}</strong></div>
<div>Reason for award: <strong>{{heroOfTheMonth.description}}</strong></div>
<div>Runners-up: <strong id="rups1">{{runnersUp}}</strong></div>
<p>Logs:</p>
<div id="logs">
<div *ngFor="let log of logs">{{log}}</div>
</div>
`;
// #docregion hero-of-the-month // #docregion hero-of-the-month
@Component({ @Component({
selector: 'hero-of-the-month', selector: 'hero-of-the-month',
template: template, templateUrl: './hero-of-the-month.component.html',
providers: [ providers: [
// #docregion use-value // #docregion use-value
{ provide: Hero, useValue: someHero }, { provide: Hero, useValue: someHero },
// #docregion provide-opaque-token // #docregion provide-injection-token
{ provide: TITLE, useValue: 'Hero of the Month' }, { provide: TITLE, useValue: 'Hero of the Month' },
// #enddocregion provide-opaque-token // #enddocregion provide-injection-token
// #enddocregion use-value // #enddocregion use-value
// #docregion use-class // #docregion use-class
{ provide: HeroService, useClass: HeroService }, { provide: HeroService, useClass: HeroService },
@ -52,9 +40,9 @@ const template = `
// #docregion use-existing // #docregion use-existing
{ provide: MinimalLogger, useExisting: LoggerService }, { provide: MinimalLogger, useExisting: LoggerService },
// #enddocregion use-existing // #enddocregion use-existing
// #docregion provide-opaque-token, use-factory // #docregion provide-injection-token, use-factory
{ provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] } { provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] }
// #enddocregion provide-opaque-token, use-factory // #enddocregion provide-injection-token, use-factory
] ]
}) })
export class HeroOfTheMonthComponent { export class HeroOfTheMonthComponent {

View File

@ -0,0 +1,22 @@
// #docregion
// Class used as a "narrowing" interface that exposes a minimal logger
// Other members of the actual implementation are invisible
export abstract class MinimalLogger {
logs: string[];
logInfo: (msg: string) => void;
}
// #enddocregion
/*
// Transpiles to:
// #docregion minimal-logger-transpiled
var MinimalLogger = (function () {
function MinimalLogger() {}
return MinimalLogger;
}());
exports("MinimalLogger", MinimalLogger);
// #enddocregion minimal-logger-transpiled
*/
// See http://stackoverflow.com/questions/43154832/unexpected-token-export-in-angular-app-with-systemjs-and-typescript/
export const _ = 0;

View File

@ -1,12 +1,12 @@
// #docplaster // #docplaster
// #docregion // #docregion
import { OpaqueToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
// #docregion runners-up // #docregion runners-up
export const RUNNERS_UP = new OpaqueToken('RunnersUp'); export const RUNNERS_UP = new InjectionToken<string>('RunnersUp');
// #enddocregion runners-up // #enddocregion runners-up
// #docregion factory-synopsis // #docregion factory-synopsis

View File

@ -1,7 +1,7 @@
// #docregion token // #docregion token
import { OpaqueToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
export let APP_CONFIG = new OpaqueToken('app.config'); export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');
// #enddocregion token // #enddocregion token
// #docregion config // #docregion config

View File

@ -8,34 +8,44 @@ include ../_util-fns
:marked :marked
# Contents # Contents
- [Application-wide dependencies](#app-wide-dependencies) * [Application-wide dependencies](#app-wide-dependencies)
- [External module configuration](#external-module-configuration) * [External module configuration](#external-module-configuration)
- [`@Injectable()` and nested service dependencies](#nested-dependencies) * [`@Injectable()` and nested service dependencies](#nested-dependencies)
- [`@Injectable()`](#injectable-1)
- [Limit service scope to a component subtree](#service-scope) * [`@Injectable()`](#injectable-1)
- [Multiple service instances (sandboxing)](#multiple-service-instances)
- [Qualify dependency lookup with `@Optional()` and `@Host()`](#qualify-dependency-lookup) * [Limit service scope to a component subtree](#service-scope)
- [Demonstration](#demonstration) * [Multiple service instances (sandboxing)](#multiple-service-instances)
- [Inject the component's DOM element](#component-element) * [Qualify dependency lookup with `@Optional()` and `@Host()`](#qualify-dependency-lookup)
- [Define dependencies with providers](#providers)
- [Defining providers](#defining-providers) * [Demonstration](#demonstration)
- [The *provide* object literal](#provide)
- [`useValue`&mdash;the *value provider*](#usevalue) * [Inject the component's DOM element](#component-element)
- [`useClass`&mdash;the *class provider*](#useclass) * [Define dependencies with providers](#providers)
- [`useExisting`&mdash;the *alias provider*](#useexisting)
- [`useFactory`&mdash;the *factory provider*](#usefactory) * [Defining providers](#defining-providers)
- [Provider token alternatives: the class-interface and `OpaqueToken`](#tokens) * [The *provide* object literal](#provide)
- [class-interface](#class-interface) * [`useValue`&mdash;the *value provider*](#usevalue)
- [`OpaqueToken`](#opaque-token) * [`useClass`&mdash;the *class provider*](#useclass)
- [Inject into a derived class](#di-inheritance) * [`useExisting`&mdash;the *alias provider*](#useexisting)
- [Find a parent component by injection](#find-parent) * [`useFactory`&mdash;the *factory provider*](#usefactory)
- [Find parent with a known component type](#known-parent)
- [Cannot find a parent by its base class](#base-parent) * [Provider token alternatives: the class-interface and `InjectionToken`](#tokens)
- [Find a parent by its class-interface](#class-interface-parent)
- [Find a parent in a tree of parents with `@SkipSelf()`](#parent-tree) * [class-interface](#class-interface)
- [The `Parent` class-interface](#parent-token) * [`InjectionToken`](#injection-token)
- [A `provideParent()` helper function](#provideparent)
- [Break circularities with a forward class reference (*forwardRef*)](#forwardref) * [Inject into a derived class](#di-inheritance)
* [Find a parent component by injection](#find-parent)
* [Find parent with a known component type](#known-parent)
* [Cannot find a parent by its base class](#base-parent)
* [Find a parent by its class-interface](#class-interface-parent)
* [Find a parent in a tree of parents with `@SkipSelf()`](#parent-tree)
* [The `Parent` class-interface](#parent-token)
* [A `provideParent()` helper function](#provideparent)
* [Break circularities with a forward class reference (*forwardRef*)](#forwardref)
:marked :marked
See the <live-example name="cb-dependency-injection"></live-example> See the <live-example name="cb-dependency-injection"></live-example>
@ -385,11 +395,11 @@ a#defining-providers
You need other ways to deliver dependency values and that means you need other ways to specify a provider. You need other ways to deliver dependency values and that means you need other ways to specify a provider.
The `HeroOfTheMonthComponent` example demonstrates many of the alternatives and why you need them. The `HeroOfTheMonthComponent` example demonstrates many of the alternatives and why you need them.
It's visually simple: a few properties and the logs produced by a logger.
figure.image-display figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/hero-of-month.png" alt="Hero of the month" width="300px") img(src="/resources/images/cookbooks/dependency-injection/hero-of-month.png" alt="Hero of the month" width="300px")
:marked :marked
It's visually simple: a few properties and the output of a logger. The code behind it gives you plenty to think about. The code behind it gives you plenty to think about.
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','hero-of-the-month','hero-of-the-month.component.ts') +makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','hero-of-the-month','hero-of-the-month.component.ts')
.l-main-section .l-main-section
@ -400,15 +410,14 @@ a(id='provide')
The `provide` object literal takes a *token* and a *definition object*. The `provide` object literal takes a *token* and a *definition object*.
The *token* is usually a class but [it doesn't have to be](#tokens). The *token* is usually a class but [it doesn't have to be](#tokens).
The *definition* object has one main property, `useValue`, that indicates how the provider The *definition* object has a required property that specifies how to create the singleton instance of the service. In this case, the property.
should create or return the provided value.
.l-main-section .l-main-section
a(id='usevalue') a(id='usevalue')
:marked :marked
#### useValue&mdash;the *value provider* #### useValue&mdash;the *value provider*
Set the `useValue` property to a ***fixed value*** that the provider can return as the dependency object. Set the `useValue` property to a ***fixed value*** that the provider can return as the service instance (AKA, the "dependency object").
Use this technique to provide *runtime configuration constants* such as website base addresses and feature flags. Use this technique to provide *runtime configuration constants* such as website base addresses and feature flags.
You can use a *value provider* in a unit test to replace a production service with a fake or mock. You can use a *value provider* in a unit test to replace a production service with a fake or mock.
@ -422,8 +431,9 @@ a(id='usevalue')
and the consumer of the injected hero would want the type information. and the consumer of the injected hero would want the type information.
The `TITLE` provider token is *not a class*. The `TITLE` provider token is *not a class*.
It's a special kind of provider lookup key called an [OpaqueToken](#opaquetoken). It's a special kind of provider lookup key called an [InjectionToken](#injection-token).
You can use an `OpaqueToken` when the dependency is a simple value like a string, a number, or a function. You can use an `InjectionToken` for any kind of provider but it's particular
helpful when the dependency is a simple value like a string, a number, or a function.
The value of a *value provider* must be defined *now*. You can't create the value later. The value of a *value provider* must be defined *now*. You can't create the value later.
Obviously the title string literal is immediately available. Obviously the title string literal is immediately available.
@ -447,7 +457,7 @@ a(id='useclass')
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-class')(format='.') +makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-class')(format='.')
:marked :marked
The first provider is the *de-sugared*, expanded form of the most typical case in which the The first provider is the *de-sugared*, expanded form of the most typical case in which the
class to be created (`HeroService`) is also the provider's injection token. class to be created (`HeroService`) is also the provider's dependency injection token.
It's in this long form to de-mystify the preferred short form. It's in this long form to de-mystify the preferred short form.
The second provider substitutes the `DateLoggerService` for the `LoggerService`. The second provider substitutes the `DateLoggerService` for the `LoggerService`.
@ -472,19 +482,25 @@ a(id='useexisting')
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-existing') +makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-existing')
:marked :marked
Narrowing an API through an aliasing interface is _one_ important use case for this technique. Narrowing an API through an aliasing interface is _one_ important use case for this technique.
This example shows aliasing for that very purpose here. The following example shows aliasing for that purpose.
Imagine that the `LoggerService` had a large API; it's actually only three methods and a property.
You'd want to shrink that API surface to just the two members exposed by the `MinimalLogger` [*class-interface*](#class-interface):
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger','src/app/date-logger.service.ts (MinimalLogger)')(format='.') Imagine that the `LoggerService` had a large API, much larger than the actual three methods and a property.
You might want to shrink that API surface to just the members you actually need.
Here the `MinimalLogger` [*class-interface*](#class-interface) reduces the API to two members:
+makeExample('cb-dependency-injection/ts/src/app/minimal-logger.service.ts', null,'src/app/minimal-logger.service.ts')(format='.')
:marked :marked
The constructor's `logger` parameter is typed as `MinimalLogger` so only its two members are visible in TypeScript: Now put it to use in a simplified version of the `HeroOfTheMonthComponent`.
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.1.ts', null,'src/app/hero-of-the-month.component.ts (minimal version)')(format='.')
:marked
The `HeroOfTheMonthComponent` constructor's `logger` parameter is typed as `MinimalLogger` so only the `logs` and `logInfo` members are visible in a TypeScript-aware editor:
figure.image-display figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png" alt="MinimalLogger restricted API") img(src="/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png" alt="MinimalLogger restricted API")
:marked :marked
Angular actually sets the `logger` parameter to the injector's full version of the `LoggerService` Behind the scenes, Angular actually sets the `logger` parameter to the full service registered under the `LoggingService` token which happens to be the `DateLoggerService` that was [provided above](#useclass).
which happens to be the `DateLoggerService`. This is because of the override provider
registered previously via `useClass`. .l-sub-section
:marked
The following image, which displays the logging date, confirms the point: The following image, which displays the logging date, confirms the point:
figure.image-display figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/date-logger-entry.png" alt="DateLoggerService entry" width="300px") img(src="/resources/images/cookbooks/dependency-injection/date-logger-entry.png" alt="DateLoggerService entry" width="300px")
@ -533,7 +549,7 @@ a(id='usefactory')
a(id="tokens") a(id="tokens")
.l-main-section .l-main-section
:marked :marked
## Provider token alternatives: the *class-interface* and *OpaqueToken* ## Provider token alternatives: the *class-interface* and *InjectionToken*
Angular dependency injection is easiest when the provider *token* is a class Angular dependency injection is easiest when the provider *token* is a class
that is also the type of the returned dependency object, or what you usually call the *service*. that is also the type of the returned dependency object, or what you usually call the *service*.
@ -550,29 +566,26 @@ a(id="tokens")
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-existing') +makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-existing')
:marked :marked
The `MinimalLogger` is an abstract class. The `MinimalLogger` is an abstract class.
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger')(format='.') +makeExample('cb-dependency-injection/ts/src/app/minimal-logger.service.ts')(format='.')
:marked :marked
You usually inherit from an abstract class. You usually inherit from an abstract class.
But `LoggerService` doesn't inherit from `MinimalLogger`. *No class* inherits from it. But *no class* in this application inherits from `MinimalLogger`.
Instead, you use it like an interface.
Look again at the declaration for `DateLoggerService`: The `LoggerService` and the `DateLoggerService` _could_ have inherited from `MinimalLogger`.
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','date-logger-service-signature')(format='.') They could have _implemented_ it instead in the manner of an interface.
:marked But they did neither.
`DateLoggerService` inherits (extends) from `LoggerService`, not `MinimalLogger`. The `MinimalLogger` is used exclusively as a dependency injection token.
The `DateLoggerService` *implements* `MinimalLogger` as if `MinimalLogger` were an *interface*.
When you use a class this way, it's called a ***class-interface***. When you use a class this way, it's called a ***class-interface***.
The key benefit of a *class-interface* is that you can get the strong-typing of an interface The key benefit of a *class-interface* is that you can get the strong-typing of an interface
and you can ***use it as a provider token*** in the same manner as a normal class. and you can ***use it as a provider token*** in the way you would a normal class.
A ***class-interface*** should define *only* the members that its consumers are allowed to call. A ***class-interface*** should define *only* the members that its consumers are allowed to call.
Such a narrowing interface helps decouple the concrete class from its consumers. Such a narrowing interface helps decouple the concrete class from its consumers.
The `MinimalLogger` defines just two of the `LoggerClass` members.
.l-sub-section .l-sub-section
:marked :marked
#### Why *MinimalLogger* is a class and not an interface #### Why *MinimalLogger* is a class and not a TypeScript interface
You can't use an interface as a provider token because You can't use an interface as a provider token because
interfaces are not JavaScript objects. interfaces are not JavaScript objects.
They exist only in the TypeScript design space. They exist only in the TypeScript design space.
@ -581,17 +594,17 @@ a(id="tokens")
A provider token must be a real JavaScript object of some kind: A provider token must be a real JavaScript object of some kind:
such as a function, an object, a string, or a class. such as a function, an object, a string, or a class.
Using a class as an interface gives you the characteristics of an interface in a JavaScript object. Using a class as an interface gives you the characteristics of an interface in a real JavaScript object.
To minimize memory cost, the class should have *no implementation*. Of course a real object occupies memory. To minimize memory cost, the class should have *no implementation*.
The `MinimalLogger` transpiles to this unoptimized, pre-minified JavaScript: The `MinimalLogger` transpiles to this unoptimized, pre-minified JavaScript for a constructor function:
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger-transpiled')(format='.') +makeExample('cb-dependency-injection/ts/src/app/minimal-logger.service.ts','minimal-logger-transpiled')(format='.')
:marked :marked
It never grows larger no matter how many members you add *as long as they are typed but not implemented*. Notice that it doesn't have a single member. It never grows no matter how many members you add to the class *as long as those members are typed but not implemented*. Look again at the TypeScript `MinimalLogger` class to confirm that it has no implementation.
a(id='opaque-token') a(id='injection-token')
:marked :marked
### _OpaqueToken_ ### _InjectionToken_
Dependency objects can be simple values like dates, numbers and strings, or Dependency objects can be simple values like dates, numbers and strings, or
shapeless objects like arrays and functions. shapeless objects like arrays and functions.
@ -601,14 +614,16 @@ a(id='opaque-token')
a JavaScript object that has a friendly name but won't conflict with a JavaScript object that has a friendly name but won't conflict with
another token that happens to have the same name. another token that happens to have the same name.
The `OpaqueToken` has these characteristics. The `InjectionToken` has these characteristics.
You encountered them twice in the *Hero of the Month* example, You encountered them twice in the *Hero of the Month* example,
in the *title* value provider and in the *runnersUp* factory provider. in the *title* value provider and in the *runnersUp* factory provider.
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','provide-opaque-token')(format='.') +makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','provide-injection-token')(format='.')
:marked :marked
You created the `TITLE` token like this: You created the `TITLE` token like this:
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','opaque-token')(format='.') +makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','injection-token')(format='.')
:marked
The type parameter, while optional, conveys the dependency's type to developers and tooling.
The token description is another developer aid.
a(id="di-inheritance") a(id="di-inheritance")
.l-main-section .l-main-section

View File

@ -251,7 +251,7 @@ a#decoration
At the core, an [`injector`](#injector) returns dependency values on request. At the core, an [`injector`](#injector) returns dependency values on request.
The expression `injector.get(token)` returns the value associated with the given token. The expression `injector.get(token)` returns the value associated with the given token.
A token is an Angular type (`OpaqueToken`). You rarely need to work with tokens directly; most A token is an Angular type (`InjectionToken`). You rarely need to work with tokens directly; most
methods accept a class name (`Foo`) or a string ("foo") and Angular converts it methods accept a class name (`Foo`) or a string ("foo") and Angular converts it
to a token. When you write `injector.get(Foo)`, the injector returns to a token. When you write `injector.get(Foo)`, the injector returns
the value associated with the token for the `Foo` class, typically an instance of `Foo` itself. the value associated with the token for the `Foo` class, typically an instance of `Foo` itself.

View File

@ -39,7 +39,7 @@ block includes
* [Dependency injection tokens](#dependency-injection-tokens) * [Dependency injection tokens](#dependency-injection-tokens)
* [Non-class dependencies](#non-class-dependencies) * [Non-class dependencies](#non-class-dependencies)
* [`OpaqueToken`](#opaquetoken) * [`InjectionToken`](#injection-token)
* [Optional dependencies](#optional) * [Optional dependencies](#optional)
* [Summary](#summary) * [Summary](#summary)
@ -645,7 +645,7 @@ a#value-provider
:marked :marked
See more `useValue` examples in the See more `useValue` examples in the
[Non-class dependencies](#non-class-dependencies) and [Non-class dependencies](#non-class-dependencies) and
[OpaqueToken](#opaquetoken) sections. [InjectionToken](#injection-token) sections.
#factory-provider #factory-provider
:marked :marked
@ -784,18 +784,20 @@ p
The TypeScript interface disappears from the generated JavaScript. The TypeScript interface disappears from the generated JavaScript.
There is no interface type information left for Angular to find at runtime. There is no interface type information left for Angular to find at runtime.
a#opaquetoken a#injection-token
:marked :marked
### _OpaqueToken_ ### _InjectionToken_
One solution to choosing a provider token for non-class dependencies is One solution to choosing a provider token for non-class dependencies is
to define and use an <a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>. to define and use an <a href="../api/core/index/InjectionToken-class.html"><b>InjectionToken</b></a>.
The definition looks like this: The definition of such a token looks like this:
+makeExample('dependency-injection/ts/src/app/app.config.ts','token')(format='.') +makeExample('dependency-injection/ts/src/app/app.config.ts','token')(format='.')
:marked :marked
Register the dependency provider using the `OpaqueToken` object: The type parameter, while optional, conveys the dependency's type to developers and tooling.
The token description is another developer aid.
Register the dependency provider using the `InjectionToken` object:
+makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-9')(format=".") +makeExample('dependency-injection/ts/src/app/providers.component.ts','providers-9')(format=".")