parent
dd2a650c34
commit
2379ad1a4b
|
@ -36,3 +36,7 @@
|
|||
<div class="di-component">
|
||||
<app-parent-finder></app-parent-finder>
|
||||
</div>
|
||||
|
||||
<div class="di-component">
|
||||
<app-storage></app-storage>
|
||||
</div>
|
||||
|
|
|
@ -9,9 +9,6 @@ import { UserService } from './user.service';
|
|||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
// #docregion providers
|
||||
providers: [ LoggerService, UserContextService, UserService ]
|
||||
// #enddocregion providers
|
||||
})
|
||||
export class AppComponent {
|
||||
// #enddocregion import-services
|
||||
|
|
|
@ -31,6 +31,7 @@ import { ParentFinderComponent,
|
|||
BarryComponent,
|
||||
BethComponent,
|
||||
BobComponent } from './parent-finder.component';
|
||||
import { StorageComponent } from './storage.component';
|
||||
|
||||
const declarations = [
|
||||
AppComponent,
|
||||
|
@ -63,6 +64,7 @@ const c_components = [
|
|||
a_components,
|
||||
b_components,
|
||||
c_components,
|
||||
StorageComponent,
|
||||
],
|
||||
bootstrap: [ AppComponent ],
|
||||
// #docregion providers
|
||||
|
|
|
@ -5,7 +5,9 @@ import { Injectable } from '@angular/core';
|
|||
import { LoggerService } from './logger.service';
|
||||
|
||||
// #docregion date-logger-service
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
// #docregion date-logger-service-signature
|
||||
export class DateLoggerService extends LoggerService
|
||||
// #enddocregion date-logger-service-signature
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HeroService {
|
||||
|
||||
// TODO: move to database
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LoggerService {
|
||||
logs: string[] = [];
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// #docregion
|
||||
import { Component, OnInit, Self, SkipSelf } from '@angular/core';
|
||||
import { BROWSER_STORAGE, BrowserStorageService } from './storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-storage',
|
||||
template: `
|
||||
Open the inspector to see the local/session storage keys:
|
||||
|
||||
<h3>Session Storage</h3>
|
||||
<button (click)="setSession()">Set Session Storage</button>
|
||||
|
||||
<h3>Local Storage</h3>
|
||||
<button (click)="setLocal()">Set Local Storage</button>
|
||||
`,
|
||||
providers: [
|
||||
BrowserStorageService,
|
||||
{ provide: BROWSER_STORAGE, useFactory: () => sessionStorage }
|
||||
]
|
||||
})
|
||||
export class StorageComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
@Self() private sessionStorageService: BrowserStorageService,
|
||||
@SkipSelf() private localStorageService: BrowserStorageService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
setSession() {
|
||||
this.sessionStorageService.set('hero', 'Mr. Nice - Session');
|
||||
}
|
||||
|
||||
setLocal() {
|
||||
this.localStorageService.set('hero', 'Mr. Nice - Local');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// #docregion
|
||||
import { Inject, Injectable, InjectionToken } from '@angular/core';
|
||||
|
||||
// #docregion storage-token
|
||||
export const BROWSER_STORAGE = new InjectionToken<Storage>('Browser Storage', {
|
||||
providedIn: 'root',
|
||||
factory: () => localStorage
|
||||
});
|
||||
// #enddocregion storage-token
|
||||
|
||||
// #docregion inject-storage-token
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BrowserStorageService {
|
||||
constructor(@Inject(BROWSER_STORAGE) public storage: Storage) {}
|
||||
|
||||
get(key: string) {
|
||||
this.storage.getItem(key);
|
||||
}
|
||||
|
||||
set(key: string, value: string) {
|
||||
this.storage.setItem(key, value);
|
||||
}
|
||||
|
||||
remove(key: string) {
|
||||
this.storage.removeItem(key);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.storage.clear();
|
||||
}
|
||||
}
|
||||
// #enddocregion inject-storage-token
|
|
@ -6,7 +6,9 @@ import { LoggerService } from './logger.service';
|
|||
import { UserService } from './user.service';
|
||||
|
||||
// #docregion injectables, injectable
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserContextService {
|
||||
// #enddocregion injectables, injectable
|
||||
name: string;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
|
||||
getUserById(userId: number): any {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { HEROES } from './mock-heroes';
|
|||
@Injectable({
|
||||
// we declare that this service should be created
|
||||
// by the root application injector.
|
||||
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class HeroService {
|
||||
|
|
|
@ -6,7 +6,6 @@ import { HEROES } from './mock-heroes';
|
|||
@Injectable({
|
||||
// we declare that this service should be created
|
||||
// by any injector that includes HeroModule.
|
||||
|
||||
providedIn: HeroModule,
|
||||
})
|
||||
export class HeroService {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// #docplaster
|
||||
// #docregion, v1
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #enddocregion v1
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class Logger {
|
||||
logs: string[] = []; // capture logs for testing
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ export class User {
|
|||
let alice = new User('Alice', true);
|
||||
let bob = new User('Bob', false);
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
user = bob; // initial user is Bob
|
||||
|
||||
|
|
|
@ -6,19 +6,14 @@ import { FormsModule } from '@angular/forms';
|
|||
import { AppComponent } from './app.component';
|
||||
import { HeroTaxReturnComponent } from './hero-tax-return.component';
|
||||
import { HeroesListComponent } from './heroes-list.component';
|
||||
import { HeroesService } from './heroes.service';
|
||||
import { VillainsListComponent } from './villains-list.component';
|
||||
|
||||
import { carComponents, carServices } from './car.components';
|
||||
import { carComponents } from './car.components';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule
|
||||
],
|
||||
providers: [
|
||||
carServices,
|
||||
HeroesService
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
|
|
|
@ -21,13 +21,17 @@ export class Tires {
|
|||
}
|
||||
|
||||
//// Engine services ///
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EngineService {
|
||||
id = 'E1';
|
||||
getEngine() { return new Engine(); }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EngineService2 {
|
||||
id = 'E2';
|
||||
getEngine() {
|
||||
|
@ -38,14 +42,18 @@ export class EngineService2 {
|
|||
}
|
||||
|
||||
//// Tire services ///
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TiresService {
|
||||
id = 'T1';
|
||||
getTires() { return new Tires(); }
|
||||
}
|
||||
|
||||
/// Car Services ///
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CarService {
|
||||
id = 'C1';
|
||||
constructor(
|
||||
|
@ -63,7 +71,9 @@ export class CarService {
|
|||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CarService2 extends CarService {
|
||||
id = 'C2';
|
||||
constructor(
|
||||
|
@ -78,7 +88,9 @@ export class CarService2 extends CarService {
|
|||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CarService3 extends CarService2 {
|
||||
id = 'C3';
|
||||
constructor(
|
||||
|
|
|
@ -13,11 +13,13 @@ import { HeroTaxReturnService } from './hero-tax-return.service';
|
|||
})
|
||||
export class HeroTaxReturnComponent {
|
||||
message = '';
|
||||
|
||||
@Output() close = new EventEmitter<void>();
|
||||
|
||||
get taxReturn(): HeroTaxReturn {
|
||||
return this.heroTaxReturnService.taxReturn;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set taxReturn (htr: HeroTaxReturn) {
|
||||
this.heroTaxReturnService.taxReturn = htr;
|
||||
|
|
|
@ -4,7 +4,9 @@ import { Observable, Observer } from 'rxjs';
|
|||
|
||||
import { Hero, HeroTaxReturn } from './hero';
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HeroesService {
|
||||
heroes: Hero[] = [
|
||||
{ id: 1, name: 'RubberMan', tid: '082-27-5678'},
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,312 @@
|
|||
# Navigate the component tree with DI
|
||||
|
||||
Application components often need to share information.
|
||||
You can often use loosely coupled techniques for sharing information,
|
||||
such as data binding and service sharing,
|
||||
but sometimes it makes sense for one component to have a direct reference to another component.
|
||||
You need a direct reference, for instance, to access values or call methods on that component.
|
||||
|
||||
Obtaining a component reference is a bit tricky in Angular.
|
||||
Angular components themselves do not have a tree that you can
|
||||
inspect or navigate programmatically. The parent-child relationship is indirect,
|
||||
established through the components' [view objects](guide/glossary#view).
|
||||
|
||||
Each component has a *host view*, and can have additional *embedded views*.
|
||||
An embedded view in component A is the
|
||||
host view of component B, which can in turn have embedded view.
|
||||
This means that there is a [view hierarchy](guide/glossary#view-hierarchy) for each component,
|
||||
of which that component's host view is the root.
|
||||
|
||||
There is an API for navigating *down* the view hierarchy.
|
||||
Check out `Query`, `QueryList`, `ViewChildren`, and `ContentChildren`
|
||||
in the [API Reference](api/).
|
||||
|
||||
There is no public API for acquiring a parent reference.
|
||||
However, because every component instance is added to an injector's container,
|
||||
you can use Angular dependency injection to reach a parent component.
|
||||
|
||||
This section describes some techniques for doing that.
|
||||
|
||||
{@a find-parent}
|
||||
{@a known-parent}
|
||||
|
||||
|
||||
### Find a parent component of known type
|
||||
|
||||
You use standard class injection to acquire a parent component whose type you know.
|
||||
|
||||
In the following example, the parent `AlexComponent` has several children including a `CathyComponent`:
|
||||
|
||||
{@a alex}
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-1" title="parent-finder.component.ts (AlexComponent v.1)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
*Cathy* reports whether or not she has access to *Alex*
|
||||
after injecting an `AlexComponent` into her constructor:
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="cathy" title="parent-finder.component.ts (CathyComponent)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Notice that even though the [@Optional](guide/dependency-injection-in-action#optional) qualifier
|
||||
is there for safety,
|
||||
the <live-example name="dependency-injection-in-action"></live-example>
|
||||
confirms that the `alex` parameter is set.
|
||||
|
||||
|
||||
{@a base-parent}
|
||||
|
||||
|
||||
### Unable to find a parent by its base class
|
||||
|
||||
What if you *don't* know the concrete parent component class?
|
||||
|
||||
A re-usable component might be a child of multiple components.
|
||||
Imagine a component for rendering breaking news about a financial instrument.
|
||||
For business reasons, this news component makes frequent calls
|
||||
directly into its parent instrument as changing market data streams by.
|
||||
|
||||
The app probably defines more than a dozen financial instrument components.
|
||||
If you're lucky, they all implement the same base class
|
||||
whose API your `NewsComponent` understands.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
|
||||
|
||||
Looking for components that implement an interface would be better.
|
||||
That's not possible because TypeScript interfaces disappear
|
||||
from the transpiled JavaScript, which doesn't support interfaces.
|
||||
There's no artifact to look for.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
This isn't necessarily good design.
|
||||
This example is examining *whether a component can
|
||||
inject its parent via the parent's base class*.
|
||||
|
||||
The sample's `CraigComponent` explores this question. [Looking back](#alex),
|
||||
you see that the `Alex` component *extends* (*inherits*) from a class named `Base`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-class-signature" title="parent-finder.component.ts (Alex class signature)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `CraigComponent` tries to inject `Base` into its `alex` constructor parameter and reports if it succeeded.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="craig" title="parent-finder.component.ts (CraigComponent)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Unfortunately, this does'nt work.
|
||||
The <live-example name="dependency-injection-in-action"></live-example>
|
||||
confirms that the `alex` parameter is null.
|
||||
*You cannot inject a parent by its base class.*
|
||||
|
||||
|
||||
|
||||
{@a class-interface-parent}
|
||||
|
||||
|
||||
### Find a parent by its class interface
|
||||
|
||||
You can find a parent component with a [class interface](guide/dependency-injection-in-action#class-interface).
|
||||
|
||||
The parent must cooperate by providing an *alias* to itself in the name of a class interface token.
|
||||
|
||||
Recall that Angular always adds a component instance to its own injector;
|
||||
that's why you could inject *Alex* into *Cathy* [earlier](#known-parent).
|
||||
|
||||
Write an [*alias provider*](guide/dependency-injection-in-action#useexisting)—a `provide` object literal with a `useExisting`
|
||||
definition—that creates an *alternative* way to inject the same component instance
|
||||
and add that provider to the `providers` array of the `@Component()` metadata for the `AlexComponent`.
|
||||
|
||||
{@a alex-providers}
|
||||
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" title="parent-finder.component.ts (AlexComponent providers)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
[Parent](#parent-token) is the provider's class interface token.
|
||||
The [*forwardRef*](guide/dependency-injection-in-action#forwardref) breaks the circular reference you just created by having the `AlexComponent` refer to itself.
|
||||
|
||||
*Carol*, the third of *Alex*'s child components, injects the parent into its `parent` parameter,
|
||||
the same way you've done it before.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="carol-class" title="parent-finder.component.ts (CarolComponent class)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here's *Alex* and family in action.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/dependency-injection-in-action/alex.png" alt="Alex in action">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
{@a parent-tree}
|
||||
|
||||
|
||||
### Find a parent in a tree with _@SkipSelf()_
|
||||
|
||||
Imagine one branch of a component hierarchy: *Alice* -> *Barry* -> *Carol*.
|
||||
Both *Alice* and *Barry* implement the `Parent' class interface.
|
||||
|
||||
*Barry* is the problem. He needs to reach his parent, *Alice*, and also be a parent to *Carol*.
|
||||
That means he must both *inject* the `Parent` class interface to get *Alice* and
|
||||
*provide* a `Parent` to satisfy *Carol*.
|
||||
|
||||
Here's *Barry*.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="barry" title="parent-finder.component.ts (BarryComponent)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
*Barry*'s `providers` array looks just like [*Alex*'s](#alex-providers).
|
||||
If you're going to keep writing [*alias providers*](guide/dependency-injection-in-action#useexisting) like this you should create a [helper function](#provideparent).
|
||||
|
||||
For now, focus on *Barry*'s constructor.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="Barry's constructor" path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="barry-ctor">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="Carol's constructor" path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="carol-ctor">
|
||||
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
It's identical to *Carol*'s constructor except for the additional `@SkipSelf` decorator.
|
||||
|
||||
`@SkipSelf` is essential for two reasons:
|
||||
|
||||
1. It tells the injector to start its search for a `Parent` dependency in a component *above* itself,
|
||||
which *is* what parent means.
|
||||
|
||||
2. Angular throws a cyclic dependency error if you omit the `@SkipSelf` decorator.
|
||||
|
||||
`Cannot instantiate cyclic dependency! (BethComponent -> Parent -> BethComponent)`
|
||||
|
||||
Here's *Alice*, *Barry*, and family in action.
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/dependency-injection-in-action/alice.png" alt="Alice in action">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
{@a parent-token}
|
||||
|
||||
|
||||
### Parent class interface
|
||||
You [learned earlier](guide/dependency-injection-in-action#class-interface) that a class interface is an abstract class used as an interface rather than as a base class.
|
||||
|
||||
The example defines a `Parent` class interface.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="parent" title="parent-finder.component.ts (Parent class-interface)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `Parent` class interface defines a `name` property with a type declaration but *no implementation*.
|
||||
The `name` property is the only member of a parent component that a child component can call.
|
||||
Such a narrow interface helps decouple the child component class from its parent components.
|
||||
|
||||
A component that could serve as a parent *should* implement the class interface as the `AliceComponent` does.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alice-class-signature" title="parent-finder.component.ts (AliceComponent class signature)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Doing so adds clarity to the code. But it's not technically necessary.
|
||||
Although `AlexComponent` has a `name` property, as required by its `Base` class,
|
||||
its class signature doesn't mention `Parent`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-class-signature" title="parent-finder.component.ts (AlexComponent class signature)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
|
||||
|
||||
`AlexComponent` *should* implement `Parent` as a matter of proper style.
|
||||
It doesn't in this example *only* to demonstrate that the code will compile and run without the interface.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a provideparent}
|
||||
|
||||
|
||||
### `provideParent()` helper function
|
||||
|
||||
Writing variations of the same parent *alias provider* gets old quickly,
|
||||
especially this awful mouthful with a [*forwardRef*](guide/dependency-injection-in-action#forwardref).
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
You can extract that logic into a helper function like the following.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="provide-the-parent" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Now you can add a simpler, more meaningful parent provider to your components.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alice-providers" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
You can do better. The current version of the helper function can only alias the `Parent` class interface.
|
||||
The application might have a variety of parent types, each with its own class interface token.
|
||||
|
||||
Here's a revised version that defaults to `parent` but also accepts an optional second parameter for a different parent class interface.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="provide-parent" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
And here's how you could use it with a different parent type.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="beth-providers" title="dependency-injection-in-action/src/app/parent-finder.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
# The Dependency Injection pattern
|
||||
|
||||
**Dependency injection** is an important application design pattern.
|
||||
It's used so widely that almost everyone just calls it _DI_.
|
||||
|
||||
Angular has its own dependency injection framework, and
|
||||
you really can't build an Angular application without it.
|
||||
|
||||
This page covers what DI is and why it's useful.
|
||||
|
||||
When you've learned the general pattern, you're ready to turn to
|
||||
the [Angular Dependency Injection](guide/dependency-injection) guide to see how it works in an Angular app.
|
||||
|
||||
{@a why-di }
|
||||
|
||||
## Why dependency injection?
|
||||
|
||||
To understand why dependency injection is so important, consider an example without it.
|
||||
Imagine writing the following code:
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-no-di.ts" region="car" title="src/app/car/car.ts (without DI)">
|
||||
</code-example>
|
||||
|
||||
The `Car` class creates everything it needs inside its constructor.
|
||||
What's the problem?
|
||||
The problem is that the `Car` class is brittle, inflexible, and hard to test.
|
||||
|
||||
This `Car` needs an engine and tires. Instead of asking for them,
|
||||
the `Car` constructor instantiates its own copies from
|
||||
the very specific classes `Engine` and `Tires`.
|
||||
|
||||
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||
That would break the `Car` class and it would stay broken until you rewrote it along the lines of
|
||||
`this.engine = new Engine(theNewParameter)`.
|
||||
The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`.
|
||||
You may not anticipate them even now.
|
||||
But you'll *have* to start caring because
|
||||
when the definition of `Engine` changes, the `Car` class must change.
|
||||
That makes `Car` brittle.
|
||||
|
||||
What if you want to put a different brand of tires on your `Car`? Too bad.
|
||||
You're locked into whatever brand the `Tires` class creates. That makes the
|
||||
`Car` class inflexible.
|
||||
|
||||
Right now each new car gets its own `engine`. It can't share an `engine` with other cars.
|
||||
While that makes sense for an automobile engine,
|
||||
surely you can think of other dependencies that should be shared, such as the onboard
|
||||
wireless connection to the manufacturer's service center. This `Car` lacks the flexibility
|
||||
to share services that have been created previously for other consumers.
|
||||
|
||||
When you write tests for `Car` you're at the mercy of its hidden dependencies.
|
||||
Is it even possible to create a new `Engine` in a test environment?
|
||||
What does `Engine` depend upon? What does that dependency depend on?
|
||||
Will a new instance of `Engine` make an asynchronous call to the server?
|
||||
You certainly don't want that going on during tests.
|
||||
|
||||
What if the `Car` should flash a warning signal when tire pressure is low?
|
||||
How do you confirm that it actually does flash a warning
|
||||
if you can't swap in low-pressure tires during the test?
|
||||
|
||||
You have no control over the car's hidden dependencies.
|
||||
When you can't control the dependencies, a class becomes difficult to test.
|
||||
|
||||
How can you make `Car` more robust, flexible, and testable?
|
||||
|
||||
{@a ctor-injection}
|
||||
That's super easy. Change the `Car` constructor to a version with DI:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/car/car.ts (excerpt with DI)" path="dependency-injection/src/app/car/car.ts" region="car-ctor">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/car/car.ts (excerpt without DI)" path="dependency-injection/src/app/car/car-no-di.ts" region="car-ctor">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
See what happened? The definition of the dependencies are
|
||||
now in the constructor.
|
||||
The `Car` class no longer creates an `engine` or `tires`.
|
||||
It just consumes them.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
This example leverages TypeScript's constructor syntax for declaring
|
||||
parameters and properties simultaneously.
|
||||
|
||||
</div>
|
||||
|
||||
Now you can create a car by passing the engine and tires to the constructor.
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation" linenums="false">
|
||||
</code-example>
|
||||
|
||||
How cool is that?
|
||||
The definition of the `engine` and `tire` dependencies are
|
||||
decoupled from the `Car` class.
|
||||
You can pass in any kind of `engine` or `tires` you like, as long as they
|
||||
conform to the general API requirements of an `engine` or `tires`.
|
||||
|
||||
Now, if someone extends the `Engine` class, that is not `Car`'s problem.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||
something like this:
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation-with-param" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The critical point is this: the `Car` class did not have to change.
|
||||
You'll take care of the consumer's problem shortly.
|
||||
|
||||
</div>
|
||||
|
||||
The `Car` class is much easier to test now because you are in complete control
|
||||
of its dependencies.
|
||||
You can pass mocks to the constructor that do exactly what you want them to do
|
||||
during each test:
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation-with-mocks" linenums="false">
|
||||
</code-example>
|
||||
|
||||
**You just learned what dependency injection is**.
|
||||
|
||||
It's a coding pattern in which a class receives its dependencies from external
|
||||
sources rather than creating them itself.
|
||||
|
||||
Cool! But what about that poor consumer?
|
||||
Anyone who wants a `Car` must now
|
||||
create all three parts: the `Car`, `Engine`, and `Tires`.
|
||||
The `Car` class shed its problems at the consumer's expense.
|
||||
You need something that takes care of assembling these parts.
|
||||
|
||||
You _could_ write a giant class to do that:
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-factory.ts" title="src/app/car/car-factory.ts">
|
||||
</code-example>
|
||||
|
||||
It's not so bad now with only three creation methods.
|
||||
But maintaining it will be hairy as the application grows.
|
||||
This factory is going to become a huge spiderweb of
|
||||
interdependent factory methods!
|
||||
|
||||
Wouldn't it be nice if you could simply list the things you want to build without
|
||||
having to define which dependency gets injected into what?
|
||||
|
||||
This is where the dependency injection framework comes into play.
|
||||
Imagine the framework had something called an _injector_.
|
||||
You register some classes with this injector, and it figures out how to create them.
|
||||
|
||||
When you need a `Car`, you simply ask the injector to get it for you and you're good to go.
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-injector.ts" region="injector-call" title="src/app/car/car-injector.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
||||
The consumer knows nothing about creating a `Car`.
|
||||
You don't have a gigantic factory class to maintain.
|
||||
Both `Car` and consumer simply ask for what they need and the injector delivers.
|
||||
|
||||
This is what a **dependency injection framework** is all about.
|
||||
|
||||
Now that you know what dependency injection is and appreciate its benefits,
|
||||
turn to the [Angular Dependency Injection](guide/dependency-injection) guide to see how it is implemented in Angular.
|
|
@ -0,0 +1,353 @@
|
|||
# Dependency Providers
|
||||
|
||||
A dependency [provider](guide/glossary#provider) configures an injector
|
||||
with a [DI token](guide/glossary#di-token),
|
||||
which that injector uses to provide the concrete, runtime version of a dependency value.
|
||||
The injector relies on the provider configuration to create instances of the dependencies
|
||||
that it injects into components, directives, pipes, and other services.
|
||||
|
||||
You must configure an injector with a provider, or it won't know how to create the dependency.
|
||||
The most obvious way for an injector to create an instance of a service class is with the class itself.
|
||||
If you specify the service class itself as the provider token, the default behavior is for the injector to instantiate that class with `new`.
|
||||
|
||||
In the following typical example, the `Logger` class itself provides a `Logger` instance.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger">
|
||||
</code-example>
|
||||
|
||||
You can, however, configure an injector with an alternative provider,
|
||||
in order to deliver some other object that provides the needed logging functionality.
|
||||
For instance:
|
||||
* You can provide a substitute class.
|
||||
|
||||
* You can provide a logger-like object.
|
||||
|
||||
* Your provider can call a logger factory function.
|
||||
|
||||
{@a provide}
|
||||
|
||||
## The `Provider` object literal
|
||||
|
||||
The class-provider syntax is a shorthand expression that expands
|
||||
into a provider configuration, defined by the [`Provider` interface](api/core/Provider).
|
||||
The following code snippets shows how a class that is given as the `providers` value is expanded into a full provider object.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger">
|
||||
</code-example>
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-3" >
|
||||
</code-example>
|
||||
|
||||
The expanded provider configuration is an object literal with two properties.
|
||||
|
||||
* The `provide` property holds the [token](guide/dependency-injection#token)
|
||||
that serves as the key for both locating a dependency value and configuring the injector.
|
||||
|
||||
* The second property is a provider definition object, which tells the injector how to create the dependency value.
|
||||
The provider-definition key can be `useClass`, as in the example.
|
||||
It can also be `useExisting`, `useValue`, or `useFactory`.
|
||||
Each of these keys provides a different type of dependency, as discussed below.
|
||||
|
||||
|
||||
{@a class-provider}
|
||||
|
||||
## Alternative class providers
|
||||
|
||||
Different classes can provide the same service.
|
||||
For example, the following code tells the injector
|
||||
to return a `BetterLogger` instance when the component asks for a logger
|
||||
using the `Logger` token.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-4" >
|
||||
</code-example>
|
||||
|
||||
{@a class-provider-dependencies}
|
||||
|
||||
### Class providers with dependencies
|
||||
|
||||
Another class, `EvenBetterLogger`, might display the user name in the log message.
|
||||
This logger gets the user from an injected `UserService` instance.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="EvenBetterLogger" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The injector needs providers for both this new logging service and its dependent `UserService`. Configure this alternative logger with the `useClass` provider-definition key, like `BetterLogger`. The following array specifies both providers in the `providers` metadata option of the parent module or component.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-5" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a aliased-class-providers}
|
||||
|
||||
### Aliased class providers
|
||||
|
||||
Suppose an old component depends upon the `OldLogger` class.
|
||||
`OldLogger` has the same interface as `NewLogger`, but for some reason
|
||||
you can't update the old component to use it.
|
||||
|
||||
When the old component logs a message with `OldLogger`,
|
||||
you want the singleton instance of `NewLogger` to handle it instead.
|
||||
In this case, the dependency injector should inject that singleton instance
|
||||
when a component asks for either the new or the old logger.
|
||||
`OldLogger` should be an *alias* for `NewLogger`.
|
||||
|
||||
If you try to alias `OldLogger` to `NewLogger` with `useClass`, you end up with two different `NewLogger` instances in your app.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6a" linenums="false">
|
||||
</code-example>
|
||||
|
||||
To make sure there is only one instance of `NewLogger`, alias `OldLogger` with the `useExisting` option.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6b" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a value-provider}
|
||||
|
||||
## Value providers
|
||||
|
||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||
To inject an object you have already created,
|
||||
configure the injector with the `useValue` option
|
||||
|
||||
The following code defines a variable that creates such an object to play the logger role.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="silent-logger" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The following provider object uses the `useValue` key to associate the variable with the `Logger` token.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-7" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a non-class-dependencies}
|
||||
|
||||
### Non-class dependencies
|
||||
|
||||
Not all dependencies are classes.
|
||||
Sometimes you want to inject a string, function, or object.
|
||||
|
||||
Apps often define configuration objects with lots of small facts,
|
||||
like the title of the application or the address of a web API endpoint.
|
||||
These configuration objects aren't always instances of a class.
|
||||
They can be object literals, as shown in the following example.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="config" title="src/app/app.config.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a interface-not-valid-token}
|
||||
|
||||
**TypeScript interfaces are not valid tokens**
|
||||
|
||||
The `HERO_DI_CONFIG` constant conforms to the `AppConfig` interface.
|
||||
Unfortunately, you cannot use a TypeScript interface as a token.
|
||||
In TypeScript, an interface is a design-time artifact, and doesn't have a runtime representation (token) that the DI framework can use.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9-interface" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="provider-9-ctor-interface" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
This might seem strange if you're used to dependency injection in strongly typed languages where an interface is the preferred dependency lookup key.
|
||||
However, JavaScript, doesn't have interfaces, so when TypeScript is transpiled to JavaScript, the interface disappears.
|
||||
There is no interface type information left for Angular to find at runtime.
|
||||
|
||||
</div>
|
||||
|
||||
One alternative is to provide and inject the configuration object in an NgModule like `AppModule`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.module.ts" region="providers" title="src/app/app.module.ts (providers)"></code-example>
|
||||
|
||||
Another solution to choosing a provider token for non-class dependencies is
|
||||
to define and use an `InjectionToken` object.
|
||||
The following example shows how to define such a token.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="token" title="src/app/app.config.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
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:
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Now you can inject the configuration object into any constructor that needs it, with
|
||||
the help of an `@Inject()` parameter decorator.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.component.2.ts" region="ctor" title="src/app/app.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
Although the `AppConfig` interface plays no role in dependency injection,
|
||||
it supports typing of the configuration object within the class.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a factory-provider}
|
||||
{@a factory-providers}
|
||||
|
||||
## Factory providers
|
||||
|
||||
Sometimes you need to create a dependent value dynamically,
|
||||
based on information you won't have until run time.
|
||||
For example, you might need information that changes repeatedly in the course of the browser session.
|
||||
Also, your injectable service might not have independent access to the source of the information.
|
||||
|
||||
In cases like this you can use a *factory provider*.
|
||||
Factory providers can also be useful when creating an instance of a dependency from
|
||||
a third-party library that wasn't designed to work with DI.
|
||||
|
||||
For example, suppose `HeroService` must hide *secret* heroes from normal users.
|
||||
Only authorized users should see secret heroes.
|
||||
|
||||
Like `EvenBetterLogger`, `HeroService` needs to know if the user is authorized to see secret heroes.
|
||||
That authorization can change during the course of a single application session,
|
||||
as when you log in a different user.
|
||||
|
||||
Let's say you don't want to inject `UserService` directly into `HeroService`, because you don't want to complicate that service with security-sensitive information.
|
||||
`HeroService` won't have direct access to the user information to decide
|
||||
who is authorized and who isn't.
|
||||
|
||||
To resolve this, we give the `HeroService` constructor a boolean flag to control display of secret heroes.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.ts" region="internals" title="src/app/heroes/hero.service.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
You can inject `Logger`, but you can't inject the `isAuthorized` flag. Instead, you can use a factory provider to create a new logger instance for `HeroService`.
|
||||
|
||||
A factory provider needs a factory function.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="factory" title="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Although `HeroService` has no access to `UserService`, the factory function does.
|
||||
You inject both `Logger` and `UserService` into the factory provider
|
||||
and let the injector pass them along to the factory function.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="provider" title="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
* The `useFactory` field tells Angular that the provider is a factory function whose implementation is `heroServiceFactory`.
|
||||
|
||||
* The `deps` property is an array of [provider tokens](guide/dependency-injection#token).
|
||||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||
|
||||
Notice that you captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||
This extra step makes the factory provider reusable.
|
||||
You can configure a provider of `HeroService` with this variable wherever you need it.
|
||||
In this sample, you need it only in `HeroesComponent`,
|
||||
where `heroServiceProvider` replaces `HeroService` in the metadata `providers` array.
|
||||
|
||||
The following shows the new and the old implementations side-by-side.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/heroes/heroes.component (v3)" path="dependency-injection/src/app/heroes/heroes.component.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/heroes/heroes.component (v2)" path="dependency-injection/src/app/heroes/heroes.component.1.ts">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
## Predefined tokens and multiple providers
|
||||
|
||||
Angular provides a number of built-in injection-token constants that you can use to customize the behavior of
|
||||
various systems.
|
||||
|
||||
For example, you can use the following built-in tokens as hooks into the framework’s bootstrapping and initialization process.
|
||||
A provider object can associate any of these injection tokens with one or more callback functions that take app-specific initialization actions.
|
||||
|
||||
* [PLATFORM_INITIALIZER](api/core/PLATFORM_INITIALIZER): Callback is invoked when a platform is initialized.
|
||||
|
||||
* [APP_BOOTSTRAP_LISTENER](api/core/APP_BOOTSTRAP_LISTENER): Callback is invoked for each component that is bootstrapped. The handler function receives the ComponentRef instance of the bootstrapped component.
|
||||
|
||||
* [APP_INITIALIZER](api/core/APP_INITIALIZER): Callback is invoked before an app is initialized. All registered initializers can optionally return a Promise. All initializer functions that return Promises must be resolved before the application is bootstrapped. If one of the initializers fails to resolves, the application is not bootstrapped.
|
||||
|
||||
The provider object can have a third option, `multi: true`, which you can use with `APP_INITIALIZER`
|
||||
to register multiple handlers for the provide event.
|
||||
|
||||
For example, when bootstrapping an application, you can register many initializers using the same token.
|
||||
|
||||
```
|
||||
export const APP_TOKENS = [
|
||||
{ provide: PLATFORM_INITIALIZER, useFactory: platformInitialized, multi: true },
|
||||
{ provide: APP_INITIALIZER, useFactory: delayBootstrapping, multi: true },
|
||||
{ provide: APP_BOOTSTRAP_LISTENER, useFactory: appBootstrapped, multi: true },
|
||||
];
|
||||
```
|
||||
|
||||
Multiple providers can be associated with a single token in other areas as well.
|
||||
For example, you can register a custom form validator using the built-in [NG_VALIDATORS](api/forms/NG_VALIDATORS) token,
|
||||
and provide multiple instances of a given validator provider by using the `multi: true` property in the provider object.
|
||||
Angular adds your custom validators to the existing collection.
|
||||
|
||||
The Router also makes use of multiple providers associated with a single token.
|
||||
When you provide multiple sets of routes using [RouterModule.forRoot](api/router/RouterModule#forroot)
|
||||
and [RouterModule.forChild](api/router/RouterModule#forchild) in a single module,
|
||||
the [ROUTES](api/router/ROUTES) token combines all the different provided sets of routes into a single value.
|
||||
|
||||
<div class="alert-is-helpful>
|
||||
|
||||
Search for [Constants in API documentation](api?type=const) to find more built-in tokens.
|
||||
|
||||
</div>
|
||||
|
||||
{@a tree-shakable-provider}
|
||||
{@a tree-shakable-providers}
|
||||
|
||||
## Tree-shakable providers
|
||||
|
||||
Tree shaking refers to a compiler option that removes code from the final bundle if that code not referenced in an application.
|
||||
When providers are tree-shakable, the Angular compiler removes the associated
|
||||
services from the final output when it determines that they are not used in your application.
|
||||
This significantly reduces the size of your bundles.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
Ideally, if an application isn't injecting a service, it shouldn't be included in the final output.
|
||||
However, Angular has to be able to identify at build time whether the service will be required or not.
|
||||
Because it's always possible to inject a service directly using `injector.get(Service)`,
|
||||
Angular can't identify all of the places in your code where this injection could happen,
|
||||
so it has no choice but to include the service in the injector.
|
||||
Thus, services provided at the NgModule or component level are not tree-shakable.
|
||||
|
||||
</div>
|
||||
|
||||
The following example of non-tree-shakable providers in Angular configures a service provider for the injector of an NgModule.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service-and-module.ts" title="src/app/tree-shaking/service-and-modules.ts" linenums="false"> </code-example>
|
||||
|
||||
This module can then be imported into your application module
|
||||
to make the service available for injection in your app,
|
||||
as shown in the following example.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/app.module.ts" title="src/app/tree-shaking/app.modules.ts" linenums="false"> </code-example>
|
||||
|
||||
When `ngc` runs, it compiles `AppModule` into a module factory, which contains definitions for all the providers declared in all the modules it includes. At runtime, this factory becomes an injector that instantiates these services.
|
||||
|
||||
Tree-shaking doesn't work here because Angular can't decide to exclude one chunk of code (the provider definition for the service within the module factory) based on whether another chunk of code (the service class) is used. To make services tree-shakable, the information about how to construct an instance of the service (the provider definition) needs to be a part of the service class itself.
|
||||
|
||||
### Creating tree-shakable providers
|
||||
|
||||
You can make a provider tree-shakable by specifying it in the `@Injectable()` decorator on the service itself, rather than in the metadata for the NgModule or component that depends on the service.
|
||||
|
||||
The following example shows the tree-shakable equivalent to the `ServiceModule` example above.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.ts" title="src/app/tree-shaking/service.ts" linenums="false"> </code-example>
|
||||
|
||||
The service can be instantiated by configuring a factory function, as in the following example.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.0.ts" title="src/app/tree-shaking/service.0.ts" linenums="false"> </code-example>
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
To override a tree-shakable provider, configure the injector of a specific NgModule or component with another provider, using the `providers: []` array syntax of the `@NgModule()` or `@Component()` decorator.
|
||||
|
||||
</div>
|
|
@ -1,23 +1,25 @@
|
|||
# Angular Dependency Injection
|
||||
# Dependency Injection in Angular
|
||||
|
||||
**Dependency Injection (DI)** is a way to create objects that depend upon other objects.
|
||||
A Dependency Injection system supplies the dependent objects (called the _dependencies_)
|
||||
when it creates an instance of an object.
|
||||
Dependency injection (DI), is an important application design pattern.
|
||||
Angular has its own DI framework, which is typically
|
||||
used in the design of Angular applications to increase their efficiency and modularity.
|
||||
|
||||
The [Dependency Injection pattern](guide/dependency-injection-pattern) page describes this general approach.
|
||||
_The guide you're reading now_ explains how Angular's own Dependency Injection system works.
|
||||
Dependencies are services or objects that a class needs to perform its function.
|
||||
DI is a coding pattern in which a class asks for dependencies from external sources rather than creating them itself.
|
||||
|
||||
## DI by example
|
||||
In Angular, the DI framework provides declared dependencies to a class when that class is instantiated. This guide explains how DI works in Angular, and how you use it to make your apps flexible, efficient, and robust, as well as testable and maintainable.
|
||||
|
||||
You'll learn Angular Dependency Injection through a discussion of the sample app that accompanies this guide.
|
||||
Run the <live-example></live-example> anytime.
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
You can run the <live-example></live-example> of the sample app that accompanies this guide.
|
||||
|
||||
</div>
|
||||
|
||||
Start by reviewing this simplified version of the _heroes_ feature
|
||||
from the [The Tour of Heroes](tutorial/).
|
||||
from the [The Tour of Heroes](tutorial/). This simple version doesn't use DI; we'll walk through converting it to do so.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="src/app/heroes/heroes.component.ts" path="dependency-injection/src/app/heroes/heroes.component.1.ts"
|
||||
region="v1">
|
||||
<code-pane title="src/app/heroes/heroes.component.ts" path="dependency-injection/src/app/heroes/heroes.component.1.ts" region="v1">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/heroes/hero-list.component.ts" path="dependency-injection/src/app/heroes/hero-list.component.1.ts">
|
||||
|
@ -31,26 +33,40 @@ from the [The Tour of Heroes](tutorial/).
|
|||
|
||||
</code-tabs>
|
||||
|
||||
The `HeroesComponent` is the top-level heroes component.
|
||||
Its only purpose is to display the `HeroListComponent`
|
||||
which displays a list of hero names.
|
||||
`HeroesComponent` is the top-level heroes component.
|
||||
Its only purpose is to display `HeroListComponent`, which displays a list of hero names.
|
||||
|
||||
This version of the `HeroListComponent` gets its `heroes` from the `HEROES` array, an in-memory collection
|
||||
This version of the `HeroListComponent` gets heroes from the `HEROES` array, an in-memory collection
|
||||
defined in a separate `mock-heroes` file.
|
||||
|
||||
<code-example title="src/app/heroes/hero-list.component.ts (class)" path="dependency-injection/src/app/heroes/hero-list.component.1.ts"
|
||||
region="class">
|
||||
<code-example title="src/app/heroes/hero-list.component.ts (class)" path="dependency-injection/src/app/heroes/hero-list.component.1.ts" region="class">
|
||||
</code-example>
|
||||
|
||||
That may suffice in the early stages of development, but it's far from ideal.
|
||||
This approach works for prototyping, but is not robust or maintainable.
|
||||
As soon as you try to test this component or get heroes from a remote server,
|
||||
you'll have to change the implementation of `HerosListComponent` and
|
||||
replace every other use of the `HEROES` mock data.
|
||||
you have to change the implementation of `HeroesListComponent` and
|
||||
replace every use of the `HEROES` mock data.
|
||||
|
||||
It's better to hide these details inside a _service_ class,
|
||||
[defined in its own file](#one-class-per-file).
|
||||
|
||||
## Create an injectable _HeroService_
|
||||
## Create and register an injectable service
|
||||
|
||||
The DI framework lets you supply data to a component from an injectable _service_ class, defined in its own file. To demonstrate, we'll create an injectable service class that provides a list of heroes, and register that class as a provider of that service.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
Having multiple classes in the same file can be confusing. We generally recommend that you define components and services in separate files.
|
||||
|
||||
If you do combine a component and service in the same file,
|
||||
it is important to define the service first, and then the component. If you define the component before the service, you get a run-time null reference error.
|
||||
|
||||
It is possible to define the component first with the help of the `forwardRef()` method as explained in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
||||
|
||||
You can also use forward references to break circular dependencies.
|
||||
See an example in the [DI Cookbook](guide/dependency-injection-in-action#forwardref).
|
||||
|
||||
</div>
|
||||
|
||||
### Create an injectable service class
|
||||
|
||||
The [**Angular CLI**](https://cli.angular.io/) can generate a new `HeroService` class in the `src/app/heroes` folder with this command.
|
||||
|
||||
|
@ -58,412 +74,84 @@ The [**Angular CLI**](https://cli.angular.io/) can generate a new `HeroService`
|
|||
ng generate service heroes/hero
|
||||
</code-example>
|
||||
|
||||
The command above creates the following `HeroService` skeleton.
|
||||
The command creates the following `HeroService` skeleton.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.0.ts" title="src/app/heroes/hero.service.ts (CLI-generated)">
|
||||
</code-example>
|
||||
|
||||
The `@Injectable` decorator is an essential ingredient in every Angular service definition.
|
||||
The rest of the class has been rewritten to expose a `getHeroes` method
|
||||
that returns the same mock data as before.
|
||||
The `@Injectable()` is an essential ingredient in every Angular service definition. The rest of the class has been written to expose a `getHeroes` method that returns the same mock data as before. (A real app would probably get its data asynchronously from a remote server, but we'll ignore that to focus on the mechanics of injecting the service.)
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.3.ts" title="src/app/heroes/hero.service.3.ts">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.3.ts" title="src/app/heroes/hero.service.ts">
|
||||
</code-example>
|
||||
|
||||
Of course, this isn't a real data service.
|
||||
If the app were actually getting data from a remote server,
|
||||
the `getHeroes` method signature would have to be asynchronous.
|
||||
|
||||
That's a defect we can safely ignore in this guide where our focus is on
|
||||
_injecting the service_ into the `HeroList` component.
|
||||
|
||||
|
||||
{@a injector-config}
|
||||
{@a bootstrap}
|
||||
|
||||
## Injectors
|
||||
### Configure an injector with a service provider
|
||||
|
||||
A _service_ like `HeroService` is just a class in Angular until you register it with an Angular dependency injector.
|
||||
The class we have created provides a service. The `@Injectable()` decorator marks it as a service
|
||||
that can be injected, but Angular can't actually inject it anywhere until you configure
|
||||
an Angular [dependency injector](guide/glossary#injector) with a [provider](guide/glossary#provider) of that service.
|
||||
|
||||
An Angular injector is responsible for creating service instances and injecting them into classes like the `HeroListComponent`.
|
||||
The injector is responsible for creating service instances and injecting them into classes like `HeroListComponent`.
|
||||
You rarely create an Angular injector yourself. Angular creates injectors for you as it executes the app, starting with the _root injector_ that it creates during the [bootstrap process](guide/bootstrapping).
|
||||
|
||||
You rarely create an Angular injector yourself.
|
||||
Angular creates injectors for you as it executes the app,
|
||||
starting with the _root injector_ that it creates during the [bootstrap process](guide/bootstrapping).
|
||||
A provider tells an injector _how to create the service_.
|
||||
You must configure an injector with a provider before that injector can create a service (or provide any other kind of dependency).
|
||||
|
||||
Angular doesn't automatically know how you want to create instances of your services or the injector to create your service. You must configure it by specifying providers for every service.
|
||||
|
||||
**Providers** tell the injector _how to create the service_.
|
||||
Without a provider, the injector would not know
|
||||
that it is responsible for injecting the service
|
||||
nor be able to create the service.
|
||||
A provider can be the service class itself, so that the injector can use `new` to create an instance.
|
||||
You might also define more than one class to provide the same service in different ways,
|
||||
and configure different injectors with different providers.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You'll learn much more about _providers_ [below](#providers).
|
||||
For now, it is sufficient to know that they configure where and how services are created.
|
||||
Injectors are inherited, which means that if a given injector can't resolve a dependency,
|
||||
it asks the parent injector to resolve it.
|
||||
A component can get services from its own injector,
|
||||
from the injectors of its component ancestors,
|
||||
from the injector of its parent NgModule, or from the `root` injector.
|
||||
|
||||
* Learn more about the [different kinds of providers](guide/dependency-injection-providers).
|
||||
|
||||
* Learn more about how the [injector hierarchy](guide/hierarchical-dependency-injection) works.
|
||||
|
||||
</div>
|
||||
|
||||
There are many ways to register a service provider with an injector. This section shows the most common ways
|
||||
of configuring a provider for your services.
|
||||
You can configure injectors with providers at different levels of your app, by setting a metadata value in one of three places:
|
||||
|
||||
{@a register-providers-injectable}
|
||||
* In the `@Injectable()` decorator for the service itself.
|
||||
|
||||
## @Injectable providers
|
||||
* In the `@NgModule()` decorator for an NgModule.
|
||||
|
||||
The `@Injectable` decorator identifies services and other classes that are intended to be injected. It can also be used to configure a provider for those services.
|
||||
* In the `@Component()` decorator for a component.
|
||||
|
||||
Here we configure a provider for `HeroService` using the `@Injectable` decorator on the class.
|
||||
The `@Injectable()` decorator has the `providedIn` metadata option, where you can specify the provider of the decorated service class with the `root` injector, or with the injector for a specific NgModule.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.0.ts" title="src/app/heroes/heroes.service.ts" linenums="false"> </code-example>
|
||||
|
||||
`providedIn` tells Angular that the root injector is responsible for creating an instance of the `HeroService` (by invoking its constructor) and making it available across the application. The CLI sets up this kind of a provider automatically for you when generating a new service.
|
||||
|
||||
Sometimes it's not desirable to have a service always be provided in the application root injector. Perhaps users should explicitly opt-in to using the service, or the service should be provided in a lazily-loaded context. In this case, the provider should be associated with a specific `@NgModule` class, and will be used by whichever injector includes that module.
|
||||
|
||||
In the following excerpt, the `@Injectable` decorator is used to configure a provider that will be available in any injector that includes the HeroModule.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.4.ts" title="src/app/heroes/hero.service.ts" linenums="false"> </code-example>
|
||||
|
||||
{@a register-providers-ngmodule}
|
||||
|
||||
### _@NgModule_ providers
|
||||
|
||||
In the following excerpt, the root `AppModule` registers two providers in its `providers` array.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (providers)" region="providers">
|
||||
</code-example>
|
||||
|
||||
The first entry registers the `UserService` class (_not shown_) under the `UserService` _injection token_.
|
||||
The second registers a value (`HERO_DI_CONFIG`) under the `APP_CONFIG` _injection token_.
|
||||
|
||||
With the above registrations, Angular can inject the `UserService` or the `HERO_DI_CONFIG` value
|
||||
into any class that it creates.
|
||||
The `@NgModule()` and `@Component()` decorators have the `providers` metadata option, where you can configure providers for NgModule-level or component-level injectors.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You'll learn about _injection tokens_ and _provider_ syntax [below](#providers).
|
||||
</div>
|
||||
Components are directives, and the `providers` option is inherited from `@Directive()`. You can also configure providers for directives and pipes at the same level as the component.
|
||||
|
||||
{@a register-providers-component}
|
||||
|
||||
### _@Component_ providers
|
||||
|
||||
In addition to providing the service application-wide or within a particular `@NgModule`, services can also be provided in specific components. Services provided in component-level is only available within that component injector or in any of its child components.
|
||||
|
||||
The example below shows a revised `HeroesComponent` that registers the `HeroService` in its `providers` array.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/heroes.component.1.ts" title="src/app/heroes/heroes.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a ngmodule-vs-comp}
|
||||
|
||||
### @Injectable, _@NgModule_ or _@Component_?
|
||||
|
||||
Should you provide a service with an `@Injectable` decorator, in an `@NgModule`, or within an `@Component`?
|
||||
The choices lead to differences in the final bundle size, service _scope_, and service _lifetime_.
|
||||
|
||||
When you register providers in the **@Injectable** decorator of the service itself, optimization tools such as those used by the CLI's production builds can perform tree shaking, which removes services that aren't used by your app. Tree shaking results in smaller bundle sizes.
|
||||
|
||||
**Angular module providers** (`@NgModule.providers`) are registered with the application's root injector.
|
||||
Angular can inject the corresponding services in any class it creates.
|
||||
Once created, a service instance lives for the life of the app and Angular injects this one service instance in every class that needs it.
|
||||
|
||||
You're likely to inject the `UserService` in many places throughout the app
|
||||
and will want to inject the same service instance every time.
|
||||
Providing the `UserService` with an Angular module is a good choice if an `@Injectable` provider is not an option..
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
To be precise, Angular module providers are registered with the root injector
|
||||
_unless the module is_ [lazy loaded](guide/lazy-loading-ngmodules).
|
||||
In this sample, all modules are _eagerly loaded_ when the application starts,
|
||||
so all module providers are registered with the app's root injector.
|
||||
|
||||
</div><br>
|
||||
|
||||
<hr>
|
||||
|
||||
**A component's providers** (`@Component.providers`) are registered with each component instance's own injector.
|
||||
|
||||
Angular can only inject the corresponding services in that component instance or one of its descendant component instances.
|
||||
Angular cannot inject the same service instance anywhere else.
|
||||
|
||||
Note that a component-provided service may have a limited lifetime. Each new instance of the component gets its own instance of the service
|
||||
and, when the component instance is destroyed, so is that service instance.
|
||||
|
||||
In this sample app, the `HeroComponent` is created when the application starts
|
||||
and is never destroyed so the `HeroService` created for the `HeroComponent` also live for the life of the app.
|
||||
|
||||
If you want to restrict `HeroService` access to the `HeroComponent` and its nested `HeroListComponent`,
|
||||
providing the `HeroService` in the `HeroComponent` may be a good choice.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The scope and lifetime of component-provided services is a consequence of [the way Angular creates component instances](#component-child-injectors).
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a providers}
|
||||
|
||||
## Providers
|
||||
|
||||
A service provider *provides* the concrete, runtime version of a dependency value.
|
||||
The injector relies on **providers** to create instances of the services
|
||||
that the injector injects into components, directives, pipes, and other services.
|
||||
|
||||
You must register a service *provider* with an injector, or it won't know how to create the service.
|
||||
|
||||
The next few sections explain the many ways you can specify a provider.
|
||||
|
||||
### The class as its own provider
|
||||
|
||||
There are many ways to *provide* something that looks and behaves like a `Logger`.
|
||||
The `Logger` class itself is an obvious and natural provider.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger">
|
||||
</code-example>
|
||||
|
||||
But it's not the only way.
|
||||
|
||||
You can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`.
|
||||
You could provide a substitute class. You could provide a logger-like object.
|
||||
You could give it a provider that calls a logger factory function.
|
||||
Any of these approaches might be a good choice under the right circumstances.
|
||||
|
||||
What matters is that the injector has a provider to go to when it needs a `Logger`.
|
||||
|
||||
{@a provide}
|
||||
|
||||
### The _provide_ object literal
|
||||
|
||||
Here's the class-provider syntax again.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger">
|
||||
</code-example>
|
||||
|
||||
This is actually a shorthand expression for a provider registration
|
||||
using a _provider_ object literal with two properties:
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-3" >
|
||||
</code-example>
|
||||
|
||||
The `provide` property holds the [token](guide/dependency-injection#token) that serves as the key for both locating a dependency value
|
||||
and registering the provider.
|
||||
|
||||
The second property is always a provider definition object,
|
||||
which you can think of as a *recipe* for creating the dependency value.
|
||||
There are many ways to create dependency values just as there are many ways to write a recipe.
|
||||
|
||||
{@a class-provider}
|
||||
|
||||
### Alternative class providers
|
||||
|
||||
Occasionally you'll ask a different class to provide the service.
|
||||
The following code tells the injector
|
||||
to return a `BetterLogger` when something asks for the `Logger`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-4" >
|
||||
</code-example>
|
||||
|
||||
{@a class-provider-dependencies}
|
||||
|
||||
### Class provider with dependencies
|
||||
|
||||
Maybe an `EvenBetterLogger` could display the user name in the log message.
|
||||
This logger gets the user from the injected `UserService`,
|
||||
which is also injected at the application level.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="EvenBetterLogger" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Configure it like `BetterLogger`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-5" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a aliased-class-providers}
|
||||
|
||||
### Aliased class providers
|
||||
|
||||
Suppose an old component depends upon an `OldLogger` class.
|
||||
`OldLogger` has the same interface as the `NewLogger`, but for some reason
|
||||
you can't update the old component to use it.
|
||||
|
||||
When the *old* component logs a message with `OldLogger`,
|
||||
you'd like the singleton instance of `NewLogger` to handle it instead.
|
||||
|
||||
The dependency injector should inject that singleton instance
|
||||
when a component asks for either the new or the old logger.
|
||||
The `OldLogger` should be an alias for `NewLogger`.
|
||||
|
||||
You certainly do not want two different `NewLogger` instances in your app.
|
||||
Unfortunately, that's what you get if you try to alias `OldLogger` to `NewLogger` with `useClass`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6a" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The solution: alias with the `useExisting` option.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6b" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a value-provider}
|
||||
|
||||
### Value providers
|
||||
|
||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="silent-logger" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Then you register a provider with the `useValue` option,
|
||||
which makes this object play the logger role.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-7" linenums="false">
|
||||
</code-example>
|
||||
|
||||
See more `useValue` examples in the
|
||||
[Non-class dependencies](guide/dependency-injection#non-class-dependencies) and
|
||||
[InjectionToken](guide/dependency-injection#injection-token) sections.
|
||||
|
||||
{@a factory-provider}
|
||||
|
||||
### Factory providers
|
||||
|
||||
Sometimes you need to create the dependent value dynamically,
|
||||
based on information you won't have until the last possible moment.
|
||||
Maybe the information changes repeatedly in the course of the browser session.
|
||||
|
||||
Suppose also that the injectable service has no independent access to the source of this information.
|
||||
|
||||
This situation calls for a **factory provider**.
|
||||
|
||||
To illustrate the point, add a new business requirement:
|
||||
the `HeroService` must hide *secret* heroes from normal users.
|
||||
Only authorized users should see secret heroes.
|
||||
|
||||
Like the `EvenBetterLogger`, the `HeroService` needs a fact about the user.
|
||||
It needs to know if the user is authorized to see secret heroes.
|
||||
That authorization can change during the course of a single application session,
|
||||
as when you log in a different user.
|
||||
|
||||
Unlike `EvenBetterLogger`, you can't inject the `UserService` into the `HeroService`.
|
||||
The `HeroService` won't have direct access to the user information to decide
|
||||
who is authorized and who is not.
|
||||
|
||||
Instead, the `HeroService` constructor takes a boolean flag to control display of secret heroes.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.ts" region="internals" title="src/app/heroes/hero.service.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
You can inject the `Logger`, but you can't inject the boolean `isAuthorized`.
|
||||
You'll have to take over the creation of new instances of this `HeroService` with a factory provider.
|
||||
|
||||
A factory provider needs a factory function:
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="factory" title="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Although the `HeroService` has no access to the `UserService`, the factory function does.
|
||||
|
||||
You inject both the `Logger` and the `UserService` into the factory provider
|
||||
and let the injector pass them along to the factory function:
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="provider" title="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The `useFactory` field tells Angular that the provider is a factory function
|
||||
whose implementation is the `heroServiceFactory`.
|
||||
|
||||
The `deps` property is an array of [provider tokens](guide/dependency-injection#token).
|
||||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||
|
||||
</div>
|
||||
|
||||
Notice that you captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||
This extra step makes the factory provider reusable.
|
||||
You can register the `HeroService` with this variable wherever you need it.
|
||||
|
||||
In this sample, you need it only in the `HeroesComponent`,
|
||||
where it replaces the previous `HeroService` registration in the metadata `providers` array.
|
||||
Here you see the new and the old implementation side-by-side:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/heroes/heroes.component (v3)" path="dependency-injection/src/app/heroes/heroes.component.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/heroes/heroes.component (v2)" path="dependency-injection/src/app/heroes/heroes.component.1.ts">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
{@a tree-shakable-provider}
|
||||
|
||||
### Tree-shakable providers
|
||||
|
||||
Tree shaking is the ability to remove code that is not referenced in an application from the final bundle. Tree-shakable providers give Angular the ability to remove services that are not used in your application from the final output. This significantly reduces the size of your bundles.
|
||||
|
||||
Ideally, if an application is not injecting a service, it should not be included in the final output. However, it turns out that the Angular compiler cannot identify at build time if the service will be required or not. Because it's always possible to inject a service directly using `injector.get(Service)`, Angular cannot identify all of the places in your code where this injection could happen, so it has no choice but to include the service in the injector regardless. Thus, services provided in modules are not tree-shakable.
|
||||
|
||||
Let us consider an example of non-tree-shakable providers in Angular.
|
||||
|
||||
In this example, to provide services in Angular, you include them in an `@NgModule`:
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service-and-module.ts" title="src/app/tree-shaking/service-and-modules.ts" linenums="false"> </code-example>
|
||||
|
||||
This module can then be imported into your application module, to make the service available for injection in your app:
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/app.module.ts" title="src/app/tree-shaking/app.modules.ts" linenums="false"> </code-example>
|
||||
|
||||
When `ngc` runs, it compiles AppModule into a module factory, which contains definitions for all the providers declared in all the modules it includes. At runtime, this factory becomes an injector that instantiates these services.
|
||||
|
||||
Tree-shaking doesn't work in the method above because Angular cannot decide to exclude one chunk of code (the provider definition for the service within the module factory) based on whether another chunk of code (the service class) is used. To make services tree-shakable, the information about how to construct an instance of the service (the provider definition) needs to be a part of the service class itself.
|
||||
|
||||
#### Creating tree-shakable providers
|
||||
|
||||
To create providers that are tree-shakable, the information that used to be specified in the module should be specified in the `@Injectable` decorator on the service itself.
|
||||
|
||||
The following example shows the tree-shakable equivalent to the `ServiceModule` example above:
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.ts" title="src/app/tree-shaking/service.ts" linenums="false"> </code-example>
|
||||
|
||||
In the example above, `providedIn` allows you to declare the injector which injects this service. Unless there is a special case, the value should always be root. Setting the value to root ensures that the service is scoped to the root injector, without naming a particular module that is present in that injector.
|
||||
|
||||
The service can be instantiated by configuring a factory function as shown below:
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.0.ts" title="src/app/tree-shaking/service.0.ts" linenums="false"> </code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
To override tree-shakable providers, register the provider using the `providers: []` array syntax of any Angular decorator that supports it.
|
||||
Learn more about [where to configure providers](guide/hierarchical-dependency-injection#where-to-register).
|
||||
|
||||
</div>
|
||||
|
||||
{@a injector-config}
|
||||
{@a bootstrap}
|
||||
|
||||
## Inject a service
|
||||
## Injecting services
|
||||
|
||||
The `HeroListComponent` should get heroes from the `HeroService`.
|
||||
In order for `HeroListComponent` to get heroes from `HeroService`, it needs to ask for `HeroService` to be injected, rather than creating it's own `HeroService` instance with `new`.
|
||||
|
||||
The component shouldn't create the `HeroService` with `new`.
|
||||
It should ask for the `HeroService` to be injected.
|
||||
|
||||
You can tell Angular to inject a dependency in the component's constructor by specifying a **constructor parameter with the dependency type**.
|
||||
Here's the `HeroListComponent` constructor, asking for the `HeroService` to be injected.
|
||||
You can tell Angular to inject a dependency in a component's constructor by specifying a **constructor parameter with the dependency type**. Here's the `HeroListComponent` constructor, asking for the `HeroService` to be injected.
|
||||
|
||||
<code-example title="src/app/heroes/hero-list.component (constructor signature)" path="dependency-injection/src/app/heroes/hero-list.component.ts"
|
||||
region="ctor-signature">
|
||||
</code-example>
|
||||
|
||||
Of course, the `HeroListComponent` should do something with the injected `HeroService`.
|
||||
Of course, `HeroListComponent` should do something with the injected `HeroService`.
|
||||
Here's the revised component, making use of the injected service, side-by-side with the previous version for comparison.
|
||||
|
||||
<code-tabs>
|
||||
|
@ -474,60 +162,40 @@ Here's the revised component, making use of the injected service, side-by-side w
|
|||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
Notice that the `HeroListComponent` doesn't know where the `HeroService` comes from.
|
||||
_You_ know that it comes from the parent `HeroesComponent`.
|
||||
If you decided instead to provide the `HeroService` in the `AppModule`,
|
||||
the `HeroListComponent` wouldn't change at all.
|
||||
The _only thing that matters_ is that the `HeroService` is provided in some parent injector.
|
||||
`HeroService` must provided in some parent injector. The code in `HeroListComponent` doesn't depend on where `HeroService` comes from.
|
||||
If you decided to provide `HeroService` in `AppModule`, `HeroListComponent` wouldn't change.
|
||||
|
||||
{@a singleton-services}
|
||||
|
||||
## Singleton services
|
||||
|
||||
Services are singletons _within the scope of an injector_.
|
||||
There is at most one instance of a service in a given injector.
|
||||
|
||||
There is only one root injector, and the `UserService` is registered with that injector.
|
||||
Therefore, there can be just one `UserService` instance in the entire app,
|
||||
and every class that injects `UserService` get this service instance.
|
||||
|
||||
However, Angular DI is a
|
||||
[hierarchical injection system](guide/hierarchical-dependency-injection),
|
||||
which means that nested injectors can create their own service instances.
|
||||
Angular creates nested injectors all the time.
|
||||
|
||||
{@a component-child-injectors}
|
||||
|
||||
## Component child injectors
|
||||
### Injector hierarchy and service instances
|
||||
|
||||
Component injectors are independent of each other and
|
||||
each of them creates its own instances of the component-provided services.
|
||||
Services are singletons _within the scope of an injector_. That is, there is at most one instance of a service in a given injector.
|
||||
|
||||
For example, when Angular creates a new instance of a component that has `@Component.providers`,
|
||||
it also creates a new _child injector_ for that instance.
|
||||
There is only one root injector for an app. Providing `UserService` at the `root` or `AppModule` level means it is registered with the root injector. There is just one `UserService` instance in the entire app and every class that injects `UserService` gets this service instance _unless_ you configure another provider with a _child injector_.
|
||||
|
||||
When Angular destroys one of these component instances, it also destroys the
|
||||
component's injector and that injector's service instances.
|
||||
Angular DI has a [hierarchical injection system](guide/hierarchical-dependency-injection), which means that nested injectors can create their own service instances.
|
||||
Angular regularly creates nested injectors. Whenever Angular creates a new instance of a component that has `providers` specified in `@Component()`, it also creates a new _child injector_ for that instance.
|
||||
Similarly, when a new NgModule is lazy-loaded at run time, Angular can create an injector for it with its own providers.
|
||||
|
||||
Because of [injector inheritance](guide/hierarchical-dependency-injection),
|
||||
Child modules and component injectors are independent of each other, and create their own separate instances of the provided services. When Angular destroys an NgModule or component instance, it also destroys that injector and that injector's service instances.
|
||||
|
||||
Thanks to [injector inheritance](guide/hierarchical-dependency-injection),
|
||||
you can still inject application-wide services into these components.
|
||||
A component's injector is a child of its parent component's injector,
|
||||
and a descendent of its parent's parent's injector, and so on all the way back to the application's _root_ injector.
|
||||
Angular can inject a service provided by any injector in that lineage.
|
||||
and a descendent of its parent's parent's injector, and so on all the way back to the application's _root_ injector. Angular can inject a service provided by any injector in that lineage.
|
||||
|
||||
For example, Angular could inject a `HeroListComponent`
|
||||
with both the `HeroService` provided in `HeroComponent`
|
||||
and the `UserService` provided in `AppModule`.
|
||||
For example, Angular can inject `HeroListComponent` with both the `HeroService` provided in `HeroComponent` and the `UserService` provided in `AppModule`.
|
||||
|
||||
{@a testing-the-component}
|
||||
|
||||
## Testing the component
|
||||
## Testing components with dependencies
|
||||
|
||||
Earlier you saw that designing a class for dependency injection makes the class easier to test.
|
||||
Designing a class with dependency injection makes the class easier to test.
|
||||
Listing dependencies as constructor parameters may be all you need to test application parts effectively.
|
||||
|
||||
For example, you can create a new `HeroListComponent` with a mock service that you can manipulate
|
||||
under test:
|
||||
under test.
|
||||
|
||||
<code-example path="dependency-injection/src/app/test.component.ts" region="spec" title="src/app/test.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
@ -540,15 +208,12 @@ Learn more in the [Testing](guide/testing) guide.
|
|||
|
||||
{@a service-needs-service}
|
||||
|
||||
## When the service needs a service
|
||||
## Services that need other services
|
||||
|
||||
The `HeroService` is very simple. It doesn't have any dependencies of its own.
|
||||
|
||||
What if it had a dependency? What if it reported its activities through a logging service?
|
||||
You'd apply the same *constructor injection* pattern,
|
||||
Service can have their own dependencies. `HeroService` is very simple and doesn't have any dependencies of its own. Suppose, however, that you want it to report its activities through a logging service. You can apply the same *constructor injection* pattern,
|
||||
adding a constructor that takes a `Logger` parameter.
|
||||
|
||||
Here is the revised `HeroService` that injects the `Logger`, side-by-side with the previous service for comparison.
|
||||
Here is the revised `HeroService` that injects `Logger`, side by side with the previous service for comparison.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
|
@ -558,154 +223,85 @@ Here is the revised `HeroService` that injects the `Logger`, side-by-side with t
|
|||
<code-pane title="src/app/heroes/hero.service (v1)" path="dependency-injection/src/app/heroes/hero.service.1.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/logger.service"
|
||||
path="dependency-injection/src/app/logger.service.ts">
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
The constructor asks for an injected instance of a `Logger` and stores it in a private field called `logger`.
|
||||
The `getHeroes()` method logs a message when asked to fetch heroes.
|
||||
The constructor asks for an injected instance of `Logger` and stores it in a private field called `logger`. The `getHeroes()` method logs a message when asked to fetch heroes.
|
||||
|
||||
Notice that the `Logger` service also has the `@Injectable()` decorator, even though it might not need its own dependencies. In fact, the `@Injectable()` decorator is **required for all services**.
|
||||
|
||||
{@a logger-service}
|
||||
When Angular creates a class whose constructor has parameters, it looks for type and injection metadata about those parameters so that it can inject the correct service.
|
||||
If Angular can't find that parameter information, it throws an error.
|
||||
Angular can only find the parameter information _if the class has a decorator of some kind_.
|
||||
The `@Injectable()` decorator is the standard decorator for service classes.
|
||||
|
||||
#### The dependent _Logger_ service
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
The sample app's `Logger` service is quite simple:
|
||||
|
||||
<code-example path="dependency-injection/src/app/logger.service.ts" title="src/app/logger.service.ts">
|
||||
</code-example>
|
||||
|
||||
If the app didn't provide this `Logger`,
|
||||
Angular would throw an exception when it looked for a `Logger` to inject
|
||||
into the `HeroService`.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ERROR Error: No provider for Logger!
|
||||
</code-example>
|
||||
|
||||
Because a singleton logger service is useful everywhere,
|
||||
it's provided in the root `AppModule`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (providers)" region="providers-2">
|
||||
</code-example>
|
||||
The decorator requirement is imposed by TypeScript. TypeScript normally discards parameter type information when it [transpiles]((guide/glossary#transpile) the code to JavaScript. TypeScript preserves this information if the class has a decorator and the `emitDecoratorMetadata` compiler option is set `true` in TypeScript's `tsconfig.json` configuration file. The CLI configures `tsconfig.json` with `emitDecoratorMetadata: true`.
|
||||
|
||||
This means you're responsible for putting `@Injectable()` on your service classes.
|
||||
|
||||
</div>
|
||||
|
||||
{@a token}
|
||||
|
||||
## Dependency injection tokens
|
||||
{@a injection-token}
|
||||
|
||||
When you register a provider with an injector, you associate that provider with a dependency injection token.
|
||||
### Dependency injection tokens
|
||||
|
||||
When you configure an injector with a provider, you associate that provider with a [DI token](guide/glossary#di-token).
|
||||
The injector maintains an internal *token-provider* map that it references when
|
||||
asked for a dependency. The token is the key to the map.
|
||||
|
||||
In all previous examples, the dependency value has been a class *instance*, and
|
||||
the class *type* served as its own lookup key.
|
||||
In simple examples, the dependency value is an *instance*, and
|
||||
the class *type* serves as its own lookup key.
|
||||
Here you get a `HeroService` directly from the injector by supplying the `HeroService` type as the token:
|
||||
|
||||
<code-example path="dependency-injection/src/app/injector.component.ts" region="get-hero-service" title="src/app/injector.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
You have similar good fortune when you write a constructor that requires an injected class-based dependency.
|
||||
The behavior is similar when you write a constructor that requires an injected class-based dependency.
|
||||
When you define a constructor parameter with the `HeroService` class type,
|
||||
Angular knows to inject the
|
||||
service associated with that `HeroService` class token:
|
||||
Angular knows to inject the service associated with that `HeroService` class token:
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero-list.component.ts" region="ctor-signature" title="src/app/heroes/hero-list.component.ts">
|
||||
</code-example>
|
||||
|
||||
This is especially convenient when you consider that most dependency values are provided by classes.
|
||||
Many dependency values are provided by classes, but not all. The expanded *provide* object lets you associate different kinds of providers with a DI token.
|
||||
|
||||
{@a non-class-dependencies}
|
||||
|
||||
### Non-class dependencies
|
||||
|
||||
What if the dependency value isn't a class? Sometimes the thing you want to inject is a
|
||||
string, function, or object.
|
||||
|
||||
Applications often define configuration objects with lots of small facts
|
||||
(like the title of the application or the address of a web API endpoint)
|
||||
but these configuration objects aren't always instances of a class.
|
||||
They can be object literals such as this one:
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="config" title="src/app/app.config.ts (excerpt)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
What if you'd like to make this configuration object available for injection?
|
||||
You know you can register an object with a [value provider](guide/dependency-injection#value-provider).
|
||||
|
||||
But what should you use as the token?
|
||||
You don't have a class to serve as a token.
|
||||
There is no `AppConfig` class.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
### TypeScript interfaces aren't valid tokens
|
||||
|
||||
The `HERO_DI_CONFIG` constant conforms to the `AppConfig` interface.
|
||||
Unfortunately, you cannot use a TypeScript interface as a token:
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9-interface" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="provider-9-ctor-interface" linenums="false">
|
||||
</code-example>
|
||||
|
||||
That seems strange if you're used to dependency injection in strongly typed languages, where
|
||||
an interface is the preferred dependency lookup key.
|
||||
|
||||
It's not Angular's doing. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces.
|
||||
The TypeScript interface disappears from the generated JavaScript.
|
||||
There is no interface type information left for Angular to find at runtime.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a injection-token}
|
||||
|
||||
### _InjectionToken_
|
||||
|
||||
One solution to choosing a provider token for non-class dependencies is
|
||||
to define and use an [*InjectionToken*](api/core/InjectionToken).
|
||||
The definition of such a token looks like this:
|
||||
|
||||
<code-example>
|
||||
import { InjectionToken } from '@angular/core';
|
||||
export const TOKEN = new InjectionToken('desc');
|
||||
</code-example>
|
||||
|
||||
You can directly configure a provider when creating an `InjectionToken`. The provider configuration determines which injector provides the token and how the value will be created. This is similar to using `@Injectable`, except that you cannot define standard providers (such as `useClass` or `useFactory`) with `InjectionToken`. Instead, you specify a factory function which returns the value to be provided directly.
|
||||
|
||||
<code-example>
|
||||
export const TOKEN =
|
||||
new InjectionToken('desc', { providedIn: 'root', factory: () => new AppConfig(), })
|
||||
</code-example>
|
||||
|
||||
Now you can inject the configuration object into any constructor that needs it, with
|
||||
the help of an `@Inject` decorator:
|
||||
|
||||
<code-example>
|
||||
constructor(@Inject(TOKEN));
|
||||
</code-example>
|
||||
|
||||
If the factory function needs access to other DI tokens, it can use the inject function from `@angular/core` to request dependencies.
|
||||
|
||||
<code-example>
|
||||
const TOKEN =
|
||||
new InjectionToken('tree-shakable token',
|
||||
{ providedIn: 'root', factory: () =>
|
||||
new AppConfig(inject(Parameter1), inject(Parameter2)), });
|
||||
</code-example>
|
||||
* Learn more about [different kinds of providers](guide/dependency-injection-providers).
|
||||
|
||||
{@a optional}
|
||||
|
||||
## Optional dependencies
|
||||
### Optional dependencies
|
||||
|
||||
You can tell Angular that the dependency is optional by annotating the constructor argument with null:
|
||||
`HeroService` *requires* a logger, but what if it could get by without
|
||||
one?
|
||||
|
||||
<code-example>
|
||||
constructor(@Inject(Token, null));
|
||||
When a component or service declares a dependency, the class constructor takes that dependency as a parameter.
|
||||
You can tell Angular that the dependency is optional by annotating the
|
||||
constructor parameter with `@Optional()`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="import-optional">
|
||||
</code-example>
|
||||
|
||||
When using optional dependencies, your code must be prepared for a null value.
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="provider-10-ctor" linenums="false">
|
||||
</code-example>
|
||||
|
||||
When using `@Optional()`, your code must be prepared for a null value. If you
|
||||
don't register a logger provider anywhere, the injector sets the
|
||||
value of `logger` to null.
|
||||
|
||||
<div class="alert-is-helpful">
|
||||
|
||||
`@Inject()` and `@Optional()` are _parameter decorators_. They alter the way the DI framework provides a dependency, by annotating the dependency parameter on the constructor of the class that requires the dependency.
|
||||
|
||||
Learn more about parameter decorators in [Hierarchical Dependency Injectors](guide/hierarchical-dependency-injection).
|
||||
|
||||
</div>
|
||||
|
||||
## Summary
|
||||
|
||||
|
@ -714,74 +310,13 @@ You can register various kinds of providers,
|
|||
and you know how to ask for an injected object (such as a service) by
|
||||
adding a parameter to a constructor.
|
||||
|
||||
Angular dependency injection is more capable than this guide has described.
|
||||
You can learn more about its advanced features, beginning with its support for
|
||||
nested injectors, in
|
||||
Dive deeper into the capabilities and advanced feature of the Angular DI system in the following pages:
|
||||
|
||||
* Learn more about nested injectors in
|
||||
[Hierarchical Dependency Injection](guide/hierarchical-dependency-injection).
|
||||
|
||||
{@a explicit-injector}
|
||||
* Learn more about [DI tokens and providers](guide/dependency-injection-providers).
|
||||
|
||||
## Appendix: Working with injectors directly
|
||||
|
||||
Developers rarely work directly with an injector, but
|
||||
here's an `InjectorComponent` that does.
|
||||
|
||||
<code-example path="dependency-injection/src/app/injector.component.ts" region="injector" title="src/app/injector.component.ts">
|
||||
</code-example>
|
||||
|
||||
An `Injector` is itself an injectable service.
|
||||
|
||||
In this example, Angular injects the component's own `Injector` into the component's constructor.
|
||||
The component then asks the injected injector for the services it wants in `ngOnInit()`.
|
||||
|
||||
Note that the services themselves are not injected into the component.
|
||||
They are retrieved by calling `injector.get()`.
|
||||
|
||||
The `get()` method throws an error if it can't resolve the requested service.
|
||||
You can call `get()` with a second parameter, which is the value to return if the service
|
||||
is not found. Angular can't find the service if it's not registered with this or any ancestor injector.
|
||||
* [Dependency Injection in Action](guide/dependency-injection-in-action) is a cookbook for some of the interesting things you can do with DI.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
|
||||
|
||||
The technique is an example of the
|
||||
[service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern).
|
||||
|
||||
**Avoid** this technique unless you genuinely need it.
|
||||
It encourages a careless grab-bag approach such as you see here.
|
||||
It's difficult to explain, understand, and test.
|
||||
You can't know by inspecting the constructor what this class requires or what it will do.
|
||||
It could acquire services from any ancestor component, not just its own.
|
||||
You're forced to spelunk the implementation to discover what it does.
|
||||
|
||||
Framework developers may take this approach when they
|
||||
must acquire services generically and dynamically.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{@a one-class-per-file}
|
||||
|
||||
## Appendix: one class per file
|
||||
|
||||
Having multiple classes in the same file is confusing and best avoided.
|
||||
Developers expect one class per file. Keep them happy.
|
||||
|
||||
If you combine the `HeroService` class with
|
||||
the `HeroesComponent` in the same file,
|
||||
**define the component last**.
|
||||
If you define the component before the service,
|
||||
you'll get a runtime null reference error.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You actually can define the component first with the help of the `forwardRef()` method as explained
|
||||
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
||||
|
||||
But it's best to avoid the problem altogether by defining components and services in separate files.
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,120 +1,216 @@
|
|||
# Hierarchical Dependency Injectors
|
||||
|
||||
You learned the basics of Angular Dependency injection in the
|
||||
[Dependency Injection](guide/dependency-injection) guide.
|
||||
|
||||
Angular has a _Hierarchical Dependency Injection_ system.
|
||||
There is actually a tree of injectors that parallel an application's component tree.
|
||||
The Angular dependency injection system is _hierarchical_.
|
||||
There is a tree of injectors that parallel an app's component tree.
|
||||
You can reconfigure the injectors at any level of that component tree.
|
||||
|
||||
This guide explores this system and how to use it to your advantage.
|
||||
It uses examples based on this <live-example></live-example>.
|
||||
|
||||
Try the <live-example></live-example>.
|
||||
{@a ngmodule-vs-comp}
|
||||
{@a where-to-register}
|
||||
|
||||
## Where to configure providers
|
||||
|
||||
You can configure providers for different injectors in the injector hierarchy.
|
||||
An internal platform-level injector is shared by all running apps.
|
||||
The `AppModule` injector is the root of an app-wide injector hierarchy, and within
|
||||
an NgModule, directive-level injectors follow the structure of the component hierarchy.
|
||||
|
||||
## The injector tree
|
||||
The choices you make about where to configure providers lead to differences in the final bundle size, service _scope_, and service _lifetime_.
|
||||
|
||||
In the [Dependency Injection](guide/dependency-injection) guide,
|
||||
you learned how to configure a dependency injector and how to retrieve dependencies where you need them.
|
||||
When you specify providers in the `@Injectable()` decorator of the service itself (typically at the app root level), optimization tools such as those used by the CLI's production builds can perform *tree shaking*, which removes services that aren't used by your app. Tree shaking results in smaller bundle sizes.
|
||||
|
||||
In fact, there is no such thing as ***the*** injector.
|
||||
An application may have multiple injectors.
|
||||
An Angular application is a tree of components. Each component instance has its own injector.
|
||||
The tree of components parallels the tree of injectors.
|
||||
* Learn more about [tree-shakable providers](guide/dependency-injection-providers#tree-shakable-providers).
|
||||
|
||||
You're likely to inject `UserService` in many places throughout the app and will want to inject the same service instance every time. Providing `UserService` through the `root` injector is a good choice, and is the default that the CLI uses when you generate a service for your app.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
<div class="alert-is-helpful">
|
||||
<header>Platform injector</header>
|
||||
|
||||
When you use `providedIn:'root'`, you are configuring the root injector for the _app_, which is the injector for `AppModule`.
|
||||
The actual root of the entire injector hierarchy is a _platform injector_ that is the parent of app-root injectors.
|
||||
This allows multiple apps to share a platform configuration. For example, a browser has only one URL bar, no matter how many apps you have running.
|
||||
|
||||
The platform injector is used internally during bootstrap, to configure platform-specific dependencies. You can configure additional platform-specific providers at the platform level by supplying `extraProviders` using the `platformBrowser()` function.
|
||||
|
||||
The component's injector may be a _proxy_ for an ancestor injector higher in the component tree.
|
||||
That's an implementation detail that improves efficiency.
|
||||
You won't notice the difference and
|
||||
your mental model should be that every component has its own injector.
|
||||
Learn more about dependency resolution through the injector hierarchy:
|
||||
[What you always wanted to know about Angular Dependency Injection tree](https://blog.angularindepth.com/angular-dependency-injection-and-tree-shakeable-tokens-4588a8f70d5d)
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
*NgModule-level* providers can be specified with `@NgModule()` `providers` metadata option, or in the `@Injectable()` `providedIn` option (with some module other than the root `AppModule`).
|
||||
|
||||
Use the `@NgModule()` `provides` option if a module is [lazy loaded](guide/lazy-loading-ngmodules). The module's own injector is configured with the provider when that module is loaded, and Angular can inject the corresponding services in any class it creates in that module. If you use the `@Injectable()` option `providedIn: MyLazyloadModule`, the provider could be shaken out at compile time, if it is not used anywhere else in the app.
|
||||
|
||||
* Learn more about [tree-shakable providers](guide/dependency-injection-providers#tree-shakable-providers).
|
||||
|
||||
For both root-level and module-level injectors, a service instance lives for the life of the app or module, and Angular injects this one service instance in every class that needs it.
|
||||
|
||||
*Component-level* providers configure each component instance's own injector.
|
||||
Angular can only inject the corresponding services in that component instance or one of its descendant component instances.
|
||||
Angular can't inject the same service instance anywhere else.
|
||||
|
||||
A component-provided service may have a limited lifetime.
|
||||
Each new instance of the component gets its own instance of the service.
|
||||
When the component instance is destroyed, so is that service instance.
|
||||
|
||||
In our sample app, `HeroComponent` is created when the application starts
|
||||
and is never destroyed,
|
||||
so the `HeroService` instance created for `HeroComponent` lives for the life of the app.
|
||||
If you want to restrict `HeroService` access to `HeroComponent` and its nested
|
||||
`HeroListComponent`, provide `HeroService` at the component level, in `HeroComponent` metadata.
|
||||
|
||||
* See more [examples of component-level injection](#component-injectors) below.
|
||||
|
||||
|
||||
{@a register-providers-injectable}
|
||||
|
||||
### @Injectable-level configuration
|
||||
|
||||
The `@Injectable()` decorator identifies every service class. The `providedIn` metadata option for a service class configures a specific injector (typically `root`)
|
||||
to use the decorated class as a provider of the service.
|
||||
When an injectable class provides its own service to the `root` injector, the service is available anywhere the class is imported.
|
||||
|
||||
The following example configures a provider for `HeroService` using the `@Injectable()` decorator on the class.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.0.ts" title="src/app/heroes/heroes.service.ts" linenums="false"> </code-example>
|
||||
|
||||
This configuration tells Angular that the app's root injector is responsible for creating an
|
||||
instance of `HeroService` by invoking its constructor,
|
||||
and for making that instance available across the application.
|
||||
|
||||
Providing a service with the app's root injector is a typical case,
|
||||
and the CLI sets up this kind of a provider automatically for you
|
||||
when generating a new service.
|
||||
However, you might not always want to provide your service at the root level.
|
||||
You might, for instance, want users to explicitly opt-in to using the service.
|
||||
|
||||
Instead of specifying the `root` injector, you can set `providedIn` to a specific NgModule.
|
||||
|
||||
For example, in the following excerpt, the `@Injectable()` decorator configures a provider
|
||||
that is available in any injector that includes the `HeroModule`.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.4.ts" title="src/app/heroes/hero.service.ts" linenums="false"> </code-example>
|
||||
|
||||
This is generally no different from configuring the injector of the NgModule itself,
|
||||
except that the service is tree-shakable if the NgModule doesn't use it.
|
||||
It can be useful for a library that offers a particular service that some
|
||||
components *might* want to inject optionally,
|
||||
and leave it up to the app whether to provide the service.
|
||||
|
||||
* Learn more about [tree-shakable providers](guide/dependency-injection-providers#tree-shakable-providers).
|
||||
|
||||
|
||||
### @NgModule-level injectors
|
||||
|
||||
You can configure a provider at the module level using the `providedIn` metadata option for a non-root NgModule, in order to limit the scope of the provider to that module.
|
||||
This is the equivalent of specifying the non-root module in the `@Injectable()` metadata, except that the service provided this way is not tree-shakable.
|
||||
|
||||
You generally don't need to specify `AppModule` with `providedIn`, because the app's `root` injector is the `AppModule` injector.
|
||||
However, if you configure a app-wide provider in the`@NgModule()` metadata for `AppModule`,
|
||||
it overrides one configured for `root` in the `@Injectable()` metadata.
|
||||
You can do this to configure a non-default provider of a service that is shared with multiple apps.
|
||||
|
||||
Here is an example of the case where the component router configuration includes
|
||||
a non-default [location strategy](guide/router#location-strategy) by listing its provider
|
||||
in the `providers` list of the `AppModule`.
|
||||
|
||||
<code-example path="dependency-injection-in-action/src/app/app.module.ts" region="providers" title="src/app/app.module.ts (providers)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
{@a register-providers-component}
|
||||
|
||||
### @Component-level injectors
|
||||
|
||||
Individual components within an NgModule have their own injectors.
|
||||
You can limit the scope of a provider to a component and its children
|
||||
by configuring the provider at the component level using the `@Component` metadata.
|
||||
|
||||
The following example is a revised `HeroesComponent` that specifies `HeroService` in its `providers` array. `HeroService` can provide heroes to instances of this component, or to any child component instances.
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/heroes.component.1.ts" title="src/app/heroes/heroes.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Element injectors
|
||||
|
||||
An injector does not actually belong to a component, but rather to the component instance's anchor element in the DOM. A different component instance on a different DOM element uses a different injector.
|
||||
|
||||
Components are a special type of directive, and the `providers` property of
|
||||
`@Component()` is inherited from `@Directive()`.
|
||||
Directives can also have dependencies, and you can configure providers
|
||||
in their `@Directive()` metadata.
|
||||
When you configure a provider for a component or directive using the `providers` property, that provider belongs to the injector for the anchor DOM element. Components and directives on the same element share an injector.
|
||||
|
||||
<!--- TBD with examples
|
||||
* For an example of how this works, see [Element-level providers](guide/dependency-injection-in-action#directive-level-providers).
|
||||
--->
|
||||
|
||||
* Learn more about [Element Injectors in Angular](https://blog.angularindepth.com/a-curios-case-of-the-host-decorator-and-element-injectors-in-angular-582562abcf0a).
|
||||
|
||||
|
||||
|
||||
## Injector bubbling
|
||||
|
||||
Consider this guide's variation on the Tour of Heroes application.
|
||||
At the top is the `AppComponent` which has some sub-components.
|
||||
One of them is the `HeroesListComponent`.
|
||||
At the top is the `AppComponent` which has some subcomponents, such as the `HeroesListComponent`.
|
||||
The `HeroesListComponent` holds and manages multiple instances of the `HeroTaxReturnComponent`.
|
||||
The following diagram represents the state of the this guide's three-level component tree when there are three instances of `HeroTaxReturnComponent`
|
||||
open simultaneously.
|
||||
|
||||
The following diagram represents the state of this three-level component tree when there are three instances of `HeroTaxReturnComponent` open simultaneously.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/dependency-injection/component-hierarchy.png" alt="injector tree">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### Injector bubbling
|
||||
|
||||
When a component requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.
|
||||
If the component's injector lacks the provider, it passes the request up to its parent component's injector.
|
||||
If that injector can't satisfy the request, it passes it along to *its* parent injector.
|
||||
If that injector can't satisfy the request, it passes the request along to the next parent injector up the tree.
|
||||
The requests keep bubbling up until Angular finds an injector that can handle the request or runs out of ancestor injectors.
|
||||
If it runs out of ancestors, Angular throws an error.
|
||||
|
||||
If you have registered a provider for the same DI token at different levels, the first one Angular encounters is the one it uses to provide the dependency. If, for example, a provider is registered locally in the component that needs a service, Angular doesn't look for another provider of the same service.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You can cap the bubbling by adding the `@Host()` parameter decorator on the dependant-service parameter
|
||||
in a component's constructor.
|
||||
The hunt for providers stops at the injector for the host element of the component.
|
||||
|
||||
* See an [example](guide/dependency-injection-in-action#qualify-dependency-lookup) of using `@Host` together with `@Optional`, another parameter decorator that lets you handle the null case if no provider is found.
|
||||
|
||||
You can cap the bubbling. An intermediate component can declare that it is the "host" component.
|
||||
The hunt for providers will climb no higher than the injector for that host component.
|
||||
This is a topic for another day.
|
||||
|
||||
* Learn more about the [`@Host` decorator and Element Injectors](https://blog.angularindepth.com/a-curios-case-of-the-host-decorator-and-element-injectors-in-angular-582562abcf0a).
|
||||
|
||||
</div>
|
||||
|
||||
If you only register providers with the root injector at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
|
||||
All requests bubble up to the root injector, whether you configured it with the `bootstrapModule` method, or registered all providers with `root` in their own services.
|
||||
|
||||
|
||||
### Re-providing a service at different levels
|
||||
|
||||
You can re-register a provider for a particular dependency token at multiple levels of the injector tree.
|
||||
You don't *have* to re-register providers. You shouldn't do so unless you have a good reason.
|
||||
But you *can*.
|
||||
|
||||
As the resolution logic works upwards, the first provider encountered wins.
|
||||
Thus, a provider in an intermediate injector intercepts a request for a service from something lower in the tree.
|
||||
It effectively "reconfigures" and "shadows" a provider at a higher level in the tree.
|
||||
|
||||
If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
|
||||
All requests bubble up to the root <code>NgModule</code> injector that you configured with the `bootstrapModule` method.
|
||||
|
||||
|
||||
{@a component-injectors}
|
||||
|
||||
## Component injectors
|
||||
|
||||
The ability to configure one or more providers at different levels opens up interesting and useful possibilities.
|
||||
The guide sample offers some scenarios where you might want to do so.
|
||||
|
||||
### Scenario: service isolation
|
||||
|
||||
Architectural reasons may lead you to restrict access to a service to the application domain where it belongs.
|
||||
|
||||
The guide sample includes a `VillainsListComponent` that displays a list of villains.
|
||||
For example, the guide sample includes a `VillainsListComponent` that displays a list of villains.
|
||||
It gets those villains from a `VillainsService`.
|
||||
|
||||
While you _could_ provide `VillainsService` in the root `AppModule` (that's where you'll find the `HeroesService`),
|
||||
that would make the `VillainsService` available everywhere in the application, including the _Hero_ workflows.
|
||||
If you provide `VillainsService` in the root `AppModule` (where you registered the `HeroesService`),
|
||||
that would make the `VillainsService` available everywhere in the application, including the _Hero_ workflows. If you later modified the `VillainsService`, you could break something in a hero component somewhere. Providing the service in the root `AppModule` creates that risk.
|
||||
|
||||
If you later modified the `VillainsService`, you could break something in a hero component somewhere.
|
||||
That's not supposed to happen but providing the service in the root `AppModule` creates that risk.
|
||||
|
||||
Instead, provide the `VillainsService` in the `providers` metadata of the `VillainsListComponent` like this:
|
||||
Instead, you can provide the `VillainsService` in the `providers` metadata of the `VillainsListComponent` like this:
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/villains-list.component.ts" linenums="false" title="src/app/villains-list.component.ts (metadata)" region="metadata">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
By providing `VillainsService` in the `VillainsListComponent` metadata and nowhere else,
|
||||
the service becomes available only in the `VillainsListComponent` and its sub-component tree.
|
||||
It's still a singleton, but it's a singleton that exist solely in the _villain_ domain.
|
||||
|
@ -144,12 +240,10 @@ Each tax return component has the following characteristics:
|
|||
<img src="generated/images/guide/dependency-injection/hid-heroes-anim.gif" alt="Heroes in action">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
One might suppose that the `HeroTaxReturnComponent` has logic to manage and restore changes.
|
||||
Suppose that the `HeroTaxReturnComponent` has logic to manage and restore changes.
|
||||
That would be a pretty easy task for a simple hero tax return.
|
||||
In the real world, with a rich tax return data model, the change management would be tricky.
|
||||
You might delegate that management to a helper service, as this example does.
|
||||
You could delegate that management to a helper service, as this example does.
|
||||
|
||||
Here is the `HeroTaxReturnService`.
|
||||
It caches a single `HeroTaxReturn`, tracks changes to that return, and can save or restore it.
|
||||
|
@ -160,8 +254,6 @@ It also delegates to the application-wide singleton `HeroService`, which it gets
|
|||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here is the `HeroTaxReturnComponent` that makes use of it.
|
||||
|
||||
|
||||
|
@ -170,36 +262,29 @@ Here is the `HeroTaxReturnComponent` that makes use of it.
|
|||
</code-example>
|
||||
|
||||
|
||||
|
||||
The _tax-return-to-edit_ arrives via the input property which is implemented with getters and setters.
|
||||
The setter initializes the component's own instance of the `HeroTaxReturnService` with the incoming return.
|
||||
The getter always returns what that service says is the current state of the hero.
|
||||
The component also asks the service to save and restore this tax return.
|
||||
|
||||
There'd be big trouble if _this_ service were an application-wide singleton.
|
||||
Every component would share the same service instance.
|
||||
Each component would overwrite the tax return that belonged to another hero.
|
||||
What a mess!
|
||||
This won't work if the service is an application-wide singleton.
|
||||
Every component would share the same service instance, and each component would overwrite the tax return that belonged to another hero.
|
||||
|
||||
Look closely at the metadata for the `HeroTaxReturnComponent`. Notice the `providers` property.
|
||||
To prevent this, we configure the component-level injector of `HeroTaxReturnComponent` to provide the service, using the `providers` property in the component metadata.
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" linenums="false" title="src/app/hero-tax-return.component.ts (providers)" region="providers">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `HeroTaxReturnComponent` has its own provider of the `HeroTaxReturnService`.
|
||||
Recall that every component _instance_ has its own injector.
|
||||
Providing the service at the component level ensures that _every_ instance of the component gets its own, private instance of the service.
|
||||
No tax return overwriting. No mess.
|
||||
Providing the service at the component level ensures that _every_ instance of the component gets its own, private instance of the service, and no tax return gets overwritten.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
|
||||
|
||||
The rest of the scenario code relies on other Angular features and techniques that you can learn about elsewhere in the documentation.
|
||||
You can review it and download it from the <live-example></live-example>.
|
||||
|
||||
|
@ -210,10 +295,9 @@ You can review it and download it from the <live-example></live-example>.
|
|||
|
||||
### Scenario: specialized providers
|
||||
|
||||
Another reason to re-provide a service is to substitute a _more specialized_ implementation of that service,
|
||||
deeper in the component tree.
|
||||
Another reason to re-provide a service at another level is to substitute a _more specialized_ implementation of that service, deeper in the component tree.
|
||||
|
||||
Consider again the Car example from the [Dependency Injection](guide/dependency-injection) guide.
|
||||
Consider a Car component that depends on several services.
|
||||
Suppose you configured the root injector (marked as A) with _generic_ providers for
|
||||
`CarService`, `EngineService` and `TiresService`.
|
||||
|
||||
|
@ -229,8 +313,6 @@ Component (B) is the parent of another component (C) that defines its own, even
|
|||
<img src="generated/images/guide/dependency-injection/car-components.png" alt="car components">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Behind the scenes, each component sets up its own injector with zero, one, or more providers defined for that component itself.
|
||||
|
||||
When you resolve an instance of `Car` at the deepest component (C),
|
||||
|
@ -247,9 +329,7 @@ its injector produces an instance of `Car` resolved by injector (C) with an `Eng
|
|||
<div class="alert is-helpful">
|
||||
|
||||
|
||||
|
||||
The code for this _cars_ scenario is in the `car.components.ts` and `car.services.ts` files of the sample
|
||||
which you can review and download from the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -88,5 +88,5 @@ Register a provider with a component when you must limit a service instance to a
|
|||
You may also be interested in:
|
||||
* [Singleton Services](guide/singleton-services), which elaborates on the concepts covered on this page.
|
||||
* [Lazy Loading Modules](guide/lazy-loading-ngmodules).
|
||||
* [Tree-shakable Providers](guide/dependency-injection#tree-shakable-providers).
|
||||
* [Tree-shakable Providers](guide/dependency-injection-providers#tree-shakable-providers).
|
||||
* [NgModule FAQ](guide/ngmodule-faq).
|
||||
|
|
|
@ -768,7 +768,7 @@ code. For example, you might have a service called `HeroesService` in AngularJS:
|
|||
<code-example path="upgrade-module/src/app/ajs-to-a-providers/heroes.service.ts" title="heroes.service.ts">
|
||||
</code-example>
|
||||
|
||||
You can upgrade the service using a Angular [factory provider](guide/dependency-injection#factory-providers)
|
||||
You can upgrade the service using a Angular [factory provider](guide/dependency-injection-providers#factory-providers)
|
||||
that requests the service from the AngularJS `$injector`.
|
||||
|
||||
Many developers prefer to declare the factory provider in a separate `ajs-upgraded-providers.ts` file
|
||||
|
|
|
@ -349,11 +349,6 @@
|
|||
"title": "Dependency Injection",
|
||||
"tooltip": "Dependency Injection: creating and injecting services",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/dependency-injection-pattern",
|
||||
"title": "The Dependency Injection pattern",
|
||||
"tooltip": "Learn about the dependency injection pattern behind the Angular DI system."
|
||||
},
|
||||
{
|
||||
"url": "guide/dependency-injection",
|
||||
"title": "Angular Dependency Injection",
|
||||
|
@ -362,12 +357,22 @@
|
|||
{
|
||||
"url": "guide/hierarchical-dependency-injection",
|
||||
"title": "Hierarchical Injectors",
|
||||
"tooltip": "Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree."
|
||||
"tooltip": "An injector tree parallels the component tree and supports nested dependencies."
|
||||
},
|
||||
{
|
||||
"url": "guide/dependency-injection-providers",
|
||||
"title": "DI Providers",
|
||||
"tooltip": "More about the different kinds of providers."
|
||||
},
|
||||
{
|
||||
"url": "guide/dependency-injection-in-action",
|
||||
"title": "DI in Action",
|
||||
"tooltip": "Techniques for Dependency Injection."
|
||||
"tooltip": "Techniques for dependency injection."
|
||||
},
|
||||
{
|
||||
"url": "guide/dependency-injection-navtree",
|
||||
"title": "Navigate the Component Tree",
|
||||
"tooltip": "Use the injection tree to find parent components."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue