parent
5a2531ee45
commit
c03186013c
|
@ -4,7 +4,8 @@
|
|||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
"!**/*.[0,1,2].*",
|
||||
"**/dummy.module.ts"
|
||||
],
|
||||
"tags": ["dependency", "di"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
Must put this interface in its own file instead of app.config.ts
|
||||
or else TypeScript gives a (bogus) warning:
|
||||
WARNING in ./src/app/... .ts
|
||||
"export 'AppConfig' was not found in './app.config'
|
||||
*/
|
||||
export interface AppConfig {
|
||||
apiEndpoint: string;
|
||||
title: string;
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
// Early versions
|
||||
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// #docregion
|
||||
// #docregion imports
|
||||
import { Component } from '@angular/core';
|
||||
import { Inject } from '@angular/core';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
import { APP_CONFIG, AppConfig } from './app.config';
|
||||
// #enddocregion imports
|
||||
|
@ -23,3 +22,5 @@ export class AppComponent {
|
|||
}
|
||||
// #enddocregion ctor
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
import { APP_CONFIG, AppConfig } from './app.config';
|
||||
import { Logger } from './logger.service';
|
||||
import { UserService } from './user.service';
|
||||
// #enddocregion imports
|
||||
|
||||
|
@ -23,8 +22,7 @@ import { UserService } from './user.service';
|
|||
<app-heroes id="authorized" *ngIf="isAuthorized"></app-heroes>
|
||||
<app-heroes id="unauthorized" *ngIf="!isAuthorized"></app-heroes>
|
||||
<app-providers></app-providers>
|
||||
`,
|
||||
providers: [Logger]
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
title: string;
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import { AppConfig } from './app-config';
|
||||
export { AppConfig } from './app-config';
|
||||
|
||||
// #docregion token
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');
|
||||
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
|
||||
// #enddocregion token
|
||||
|
||||
// #docregion config
|
||||
export interface AppConfig {
|
||||
apiEndpoint: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const HERO_DI_CONFIG: AppConfig = {
|
||||
apiEndpoint: 'api.heroes.com',
|
||||
title: 'Dependency Injection'
|
||||
|
|
|
@ -1,32 +1,24 @@
|
|||
// #docplaster
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { APP_CONFIG, HERO_DI_CONFIG } from './app.config';
|
||||
import { AppComponent } from './app.component';
|
||||
import { CarComponent } from './car/car.component';
|
||||
import { HeroesComponent } from './heroes/heroes.component';
|
||||
import { HeroListComponent } from './heroes/hero-list.component';
|
||||
import { InjectorComponent } from './injector.component';
|
||||
import { Logger } from './logger.service';
|
||||
import { TestComponent } from './test.component';
|
||||
import { APP_CONFIG, HERO_DI_CONFIG } from './app.config';
|
||||
import { UserService } from './user.service';
|
||||
import {
|
||||
ProvidersComponent,
|
||||
Provider1Component,
|
||||
Provider3Component,
|
||||
Provider4Component,
|
||||
Provider5Component,
|
||||
Provider6aComponent,
|
||||
Provider6bComponent,
|
||||
Provider7Component,
|
||||
Provider8Component,
|
||||
Provider9Component,
|
||||
Provider10Component,
|
||||
} from './providers.component';
|
||||
|
||||
import { ProvidersModule } from './providers.module';
|
||||
|
||||
// #docregion ngmodule
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
BrowserModule,
|
||||
ProvidersModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
|
@ -35,26 +27,19 @@ import {
|
|||
// #enddocregion ngmodule
|
||||
HeroListComponent,
|
||||
InjectorComponent,
|
||||
TestComponent,
|
||||
ProvidersComponent,
|
||||
Provider1Component,
|
||||
Provider3Component,
|
||||
Provider4Component,
|
||||
Provider5Component,
|
||||
Provider6aComponent,
|
||||
Provider6bComponent,
|
||||
Provider7Component,
|
||||
Provider8Component,
|
||||
Provider9Component,
|
||||
Provider10Component,
|
||||
TestComponent
|
||||
// #docregion ngmodule
|
||||
],
|
||||
// #docregion ngmodule-providers
|
||||
// #docregion providers, providers-2
|
||||
providers: [
|
||||
// #enddocregion providers
|
||||
Logger,
|
||||
// #docregion providers
|
||||
UserService,
|
||||
{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
|
||||
],
|
||||
// #enddocregion ngmodule-providers
|
||||
// #enddocregion providers, providers-2
|
||||
exports: [ CarComponent, HeroesComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
/// Dummy modules to satisfy Angular Language Service
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
////////
|
||||
|
||||
import { AppComponent as AppComponent1 } from './app.component.1';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, AppModule ],
|
||||
declarations: [ AppComponent1 ]
|
||||
})
|
||||
export class DummyModule1 {}
|
||||
|
||||
/////////
|
||||
|
||||
import { AppComponent as AppComponent2 } from './app.component.2';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, AppModule ],
|
||||
declarations: [ AppComponent2 ]
|
||||
})
|
||||
export class DummyModule2 {}
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
/// Dummy modules to satisfy Angular Language Service
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
////////
|
||||
|
||||
import { HeroListComponent as HeroListComponent1 } from './hero-list.component.1';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ HeroListComponent1 ],
|
||||
exports: [ HeroListComponent1 ]
|
||||
})
|
||||
export class DummyModule1 {}
|
||||
|
||||
/////////
|
||||
|
||||
import { HeroListComponent as HeroListComponent2 } from './hero-list.component.2';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ HeroListComponent2 ]
|
||||
})
|
||||
export class DummyModule2 {}
|
||||
|
||||
/////////
|
||||
|
||||
import { HeroesComponent as HeroesComponent1 } from './heroes.component.1';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, DummyModule1 ],
|
||||
declarations: [ HeroesComponent1 ]
|
||||
})
|
||||
export class DummyModule3 {}
|
|
@ -1,6 +1,5 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Component({
|
||||
|
@ -11,6 +10,8 @@ import { HEROES } from './mock-heroes';
|
|||
</div>
|
||||
`
|
||||
})
|
||||
// #docregion class
|
||||
export class HeroListComponent {
|
||||
heroes = HEROES;
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
// #enddocregion
|
||||
import { HeroService } from './hero.service.1';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* tslint:disable:one-line */
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
constructor() { }
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { Logger } from '../logger.service';
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { Logger } from '../logger.service';
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
// #docplaster
|
||||
// #docregion full, v1
|
||||
// #docregion, v1
|
||||
import { Component } from '@angular/core';
|
||||
// #enddocregion v1
|
||||
|
||||
import { HeroService } from './hero.service';
|
||||
// #enddocregion full
|
||||
|
||||
// #docregion full, v1
|
||||
|
||||
// #docregion v1
|
||||
@Component({
|
||||
selector: 'app-heroes',
|
||||
// #enddocregion v1
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { heroServiceProvider } from './hero.service.provider';
|
||||
|
||||
@Component({
|
||||
selector: 'app-heroes',
|
||||
providers: [ heroServiceProvider ],
|
||||
template: `
|
||||
<h2>Heroes</h2>
|
||||
<app-hero-list></app-hero-list>
|
||||
`,
|
||||
providers: [heroServiceProvider]
|
||||
`
|
||||
})
|
||||
export class HeroesComponent { }
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
/* tslint:disable:one-line:check-open-brace*/
|
||||
// Examples of provider arrays
|
||||
// #docplaster
|
||||
/*
|
||||
* A collection of demo components showing different ways to provide services
|
||||
* in @Component metadata
|
||||
*/
|
||||
import { Component, Inject, Injectable, OnInit } from '@angular/core';
|
||||
|
||||
import { APP_CONFIG, AppConfig,
|
||||
import {
|
||||
APP_CONFIG,
|
||||
AppConfig,
|
||||
HERO_DI_CONFIG } from './app.config';
|
||||
|
||||
import { HeroService } from './heroes/hero.service';
|
||||
|
@ -11,9 +14,8 @@ import { heroServiceProvider } from './heroes/hero.service.provider';
|
|||
import { Logger } from './logger.service';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
let template = '{{log}}';
|
||||
const template = '{{log}}';
|
||||
|
||||
//////////////////////////////////////////
|
||||
@Component({
|
||||
selector: 'provider-1',
|
||||
template: template,
|
||||
|
@ -30,6 +32,7 @@ export class Provider1Component {
|
|||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
@Component({
|
||||
selector: 'provider-3',
|
||||
template: template,
|
||||
|
@ -47,7 +50,7 @@ export class Provider3Component {
|
|||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
class BetterLogger extends Logger {}
|
||||
export class BetterLogger extends Logger {}
|
||||
|
||||
@Component({
|
||||
selector: 'provider-4',
|
||||
|
@ -66,9 +69,10 @@ export class Provider4Component {
|
|||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
// #docregion EvenBetterLogger
|
||||
@Injectable()
|
||||
class EvenBetterLogger extends Logger {
|
||||
export class EvenBetterLogger extends Logger {
|
||||
constructor(private userService: UserService) { super(); }
|
||||
|
||||
log(message: string) {
|
||||
|
@ -96,8 +100,10 @@ export class Provider5Component {
|
|||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
class NewLogger extends Logger {}
|
||||
class OldLogger {
|
||||
|
||||
export class NewLogger extends Logger {}
|
||||
|
||||
export class OldLogger {
|
||||
logs: string[] = [];
|
||||
log(message: string) {
|
||||
throw new Error('Should not call the old logger!');
|
||||
|
@ -149,11 +155,14 @@ export class Provider6bComponent {
|
|||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
// #docregion silent-logger
|
||||
// An object in the shape of the logger service
|
||||
let silentLogger = {
|
||||
export function SilentLoggerFn() {}
|
||||
|
||||
const silentLogger = {
|
||||
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
|
||||
log: () => {}
|
||||
log: SilentLoggerFn
|
||||
};
|
||||
// #enddocregion silent-logger
|
||||
|
||||
|
@ -172,6 +181,7 @@ export class Provider7Component {
|
|||
this.log = logger.logs[0];
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
|
||||
@Component({
|
||||
|
@ -189,6 +199,7 @@ export class Provider8Component {
|
|||
}
|
||||
|
||||
/////////////////
|
||||
|
||||
@Component({
|
||||
selector: 'provider-9',
|
||||
template: template,
|
||||
|
@ -218,6 +229,7 @@ export class Provider9Component implements OnInit {
|
|||
this.log = 'APP_CONFIG Application title is ' + this.config.title;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Sample providers 1 to 7 illustrate a required logger dependency.
|
||||
// Optional logger, can be null
|
||||
|
@ -248,6 +260,7 @@ export class Provider10Component implements OnInit {
|
|||
}
|
||||
|
||||
/////////////////
|
||||
|
||||
@Component({
|
||||
selector: 'app-providers',
|
||||
template: `
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
|
||||
import {
|
||||
Provider1Component,
|
||||
Provider3Component,
|
||||
Provider4Component,
|
||||
Provider5Component,
|
||||
Provider6aComponent,
|
||||
Provider6bComponent,
|
||||
Provider7Component,
|
||||
Provider8Component,
|
||||
Provider9Component,
|
||||
Provider10Component,
|
||||
ProvidersComponent,
|
||||
} from './providers.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
Provider1Component,
|
||||
Provider3Component,
|
||||
Provider4Component,
|
||||
Provider5Component,
|
||||
Provider6aComponent,
|
||||
Provider6bComponent,
|
||||
Provider7Component,
|
||||
Provider8Component,
|
||||
Provider9Component,
|
||||
Provider10Component,
|
||||
ProvidersComponent,
|
||||
],
|
||||
exports: [ ProvidersComponent ]
|
||||
})
|
||||
export class ProvidersModule {}
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero } from './heroes/hero';
|
||||
import { HeroService } from './heroes/hero.service';
|
||||
import { HeroListComponent } from './heroes/hero-list.component';
|
||||
|
||||
|
@ -22,12 +23,13 @@ export class TestComponent {
|
|||
function runTests() {
|
||||
|
||||
// #docregion spec
|
||||
let expectedHeroes = [{name: 'A'}, {name: 'B'}]
|
||||
let mockService = <HeroService> {getHeroes: () => expectedHeroes }
|
||||
const expectedHeroes = [{name: 'A'}, {name: 'B'}]
|
||||
const mockService = <HeroService> {getHeroes: () => expectedHeroes }
|
||||
|
||||
it('should have heroes when HeroListComponent created', () => {
|
||||
let hlc = new HeroListComponent(mockService);
|
||||
expect(hlc.heroes.length).toEqual(expectedHeroes.length);
|
||||
// Pass the mock to the constructor as the Angular injector would
|
||||
const component = new HeroListComponent(mockService);
|
||||
expect(component.heroes.length).toEqual(expectedHeroes.length);
|
||||
});
|
||||
// #enddocregion spec
|
||||
|
||||
|
|
|
@ -5,51 +5,6 @@ Dependency Injection is a powerful pattern for managing code dependencies.
|
|||
This cookbook explores many of the features of Dependency Injection (DI) in Angular.
|
||||
{@a toc}
|
||||
|
||||
<!--
|
||||
|
||||
# Contents
|
||||
|
||||
* [Application-wide dependencies](guide/dependency-injection-in-action#app-wide-dependencies)
|
||||
* [External module configuration](guide/dependency-injection-in-action#external-module-configuration)
|
||||
* [`@Injectable()` and nested service dependencies](guide/dependency-injection-in-action#nested-dependencies)
|
||||
|
||||
* [`@Injectable()`](guide/dependency-injection-in-action#injectable-1)
|
||||
|
||||
* [Limit service scope to a component subtree](guide/dependency-injection-in-action#service-scope)
|
||||
* [Multiple service instances (sandboxing)](guide/dependency-injection-in-action#multiple-service-instances)
|
||||
* [Qualify dependency lookup with `@Optional()` and `@Host()`](guide/dependency-injection-in-action#qualify-dependency-lookup)
|
||||
|
||||
* [Demonstration](guide/dependency-injection-in-action#demonstration)
|
||||
|
||||
* [Inject the component's DOM element](guide/dependency-injection-in-action#component-element)
|
||||
* [Define dependencies with providers](guide/dependency-injection-in-action#providers)
|
||||
|
||||
* [Defining providers](guide/dependency-injection-in-action#defining-providers)
|
||||
* [The *provide* object literal](guide/dependency-injection-in-action#provide)
|
||||
* [`useValue`—the *value provider*](guide/dependency-injection-in-action#usevalue)
|
||||
* [`useClass`—the *class provider*](guide/dependency-injection-in-action#useclass)
|
||||
* [`useExisting`—the *alias provider*](guide/dependency-injection-in-action#useexisting)
|
||||
* [`useFactory`—the *factory provider*](guide/dependency-injection-in-action#usefactory)
|
||||
|
||||
* [Provider token alternatives: the class-interface and `InjectionToken`](guide/dependency-injection-in-action#tokens)
|
||||
|
||||
* [class-interface](guide/dependency-injection-in-action#class-interface)
|
||||
* [`InjectionToken`](guide/dependency-injection-in-action#injection-token)
|
||||
|
||||
* [Inject into a derived class](guide/dependency-injection-in-action#di-inheritance)
|
||||
* [Find a parent component by injection](guide/dependency-injection-in-action#find-parent)
|
||||
|
||||
* [Find parent with a known component type](guide/dependency-injection-in-action#known-parent)
|
||||
* [Cannot find a parent by its base class](guide/dependency-injection-in-action#base-parent)
|
||||
* [Find a parent by its class-interface](guide/dependency-injection-in-action#class-interface-parent)
|
||||
* [Find a parent in a tree of parents with `@SkipSelf()`](guide/dependency-injection-in-action#parent-tree)
|
||||
* [The `Parent` class-interface](guide/dependency-injection-in-action#parent-token)
|
||||
* [A `provideParent()` helper function](guide/dependency-injection-in-action#provideparent)
|
||||
|
||||
* [Break circularities with a forward class reference (*forwardRef*)](guide/dependency-injection-in-action#forwardref)
|
||||
|
||||
-->
|
||||
|
||||
See the <live-example name="dependency-injection-in-action"></live-example>
|
||||
of the code in this cookbook.
|
||||
|
||||
|
@ -79,7 +34,7 @@ is all the registration you need.
|
|||
|
||||
A *provider* is something that can create or deliver a service.
|
||||
Angular creates a service instance from a class provider by using `new`.
|
||||
Read more about providers in the [Dependency Injection](guide/dependency-injection#injector-providers)
|
||||
Read more about providers in the [Dependency Injection](guide/dependency-injection#register-providers-ngmodule)
|
||||
guide.
|
||||
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
# 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="l-sub-section">
|
||||
|
||||
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="l-sub-section">
|
||||
|
||||
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.
|
File diff suppressed because it is too large
Load Diff
|
@ -243,10 +243,15 @@
|
|||
"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": "Dependency Injection",
|
||||
"tooltip": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\"."
|
||||
"title": "Angular Dependency Injection",
|
||||
"tooltip": "Angular's dependency injection system creates and delivers dependent services to Angular-created classes."
|
||||
},
|
||||
{
|
||||
"url": "guide/hierarchical-dependency-injection",
|
||||
|
|
Loading…
Reference in New Issue