在一個項目中,組件和服務之間存在錯綜複雜的關係,爲了最小程度的耦合,咱們須要來管理組織這種關係,依賴注入就是管理這種關係的一種方式。javascript
在學習一個概念以前,咱們必需要知道咱們爲何要學習這個東西,這個東西究竟解決了什麼問題。就比如這裏講到的,依賴注入究竟解決了什麼問題。要解決這個問題,咱們先來看看示例代碼:css
export class Car {
public engine: Engine;
public tires: Tires;
public description = 'No DI';
constructor() {
this.engine = new Engine();
this.tires = new Tires();
}
// Method using the engine and tires
drive() {
return `${this.description} car with ` +
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
}
}
複製代碼
以上是來自angular官網的一段代碼,咱們能夠看到一個Car
類依賴於Engine
和Tires
這兩個類,咱們在Car
的構造函數中去實例這兩個依賴類。這有什麼問題?若是有一天咱們的Tires
構造函數須要一個參數,那麼咱們必需要在Car
的構造函數中去更改代碼。html
// ...
constructor() {
this.engine = new Engine();
this.tires = new Tires(params);
}
]
// ...
複製代碼
這種代碼是很是不靈活的。雖然咱們能夠進行以下結構調整java
export class Car {
public engine: Engine;
public tires: Tires;
public description = 'No DI';
constructor(engine, tires) {
this.engine = engine;
this.tires = tires;
}
// Method using the engine and tires
drive() {
return `${this.description} car with ` +
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
}
}
const car = new Car(new Engine(), new Tires())
複製代碼
這樣彷佛解決了不靈活的問題,可是若是依賴項不少的話,咱們都要去手動建立這些實例,也不太方便。其實建立依賴實例的過程徹底能夠交給一個專門的'工廠'來作,這就是angular裏面的Injector。app
@Component({
selector: 'app-heroes',
providers: [Engine, Tires],
template: ` <h2>Heroes</h2> <app-hero-list></app-hero-list> `
})
export class HeroesComponent {
construtor(private engine: Engine) {
this.engine.start();
}
}
複製代碼
在Angular中,通常咱們將這些公共的依賴都會一些一個服務裏面。在上面的用法咱們能夠看到多了一個providers,另外就是在類的構造函數中增長了private engine: Engine
咱們就能夠去使用engine這個實例了,在這個過程當中,咱們並無去手動去建立依賴項的實例。這是由於angular的Injector幫咱們自動建立了。在這裏有一個比較形象的比喻就是,一個廚子(Injector)根據菜譜(providers)去作菜(依賴的實例),可是究竟作哪些菜呢,客人說了算(private engine: Engine
也就是構造函數中的)ide
import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
constructor(private engine: Engine) { }
}
複製代碼
若是咱們的一個服務自己就依賴於其餘依賴項,那麼咱們使用@Injectable()
裝飾器(即便一個服務並無依賴於其餘服務,咱們也推薦加上@Injectable()裝飾器),咱們依然要提供providers。這裏因爲服務一般跟視圖是沒有具體的關係,因此這裏咱們不會引入@component
裝飾器,那麼咱們在哪裏肯定這個providers呢?咱們能夠在一個module
中的providers屬性中去定義,那麼這個module
中的全部組件都會去共用這一個實例,可是咱們有時候咱們不但願共用一個實例,而是一個新的實例,那麼咱們能夠在這個組件中的providers中從新定義,這樣咱們就會獲得一個新的實例。實際上這就是層級注入。利用層級注入咱們既能夠共用實例,也能夠不共用實例很是方便。通常全局使用的服務,咱們會註冊在app.module模塊之下,這樣在整個應用中均可以使用。函數
在上面咱們說過經過依賴注入建立的實例是能夠實現共享的,咱們證實一下。學習
import { Component, OnInit, ReflectiveInjector } from '@angular/core';
import {DependenceComponent} from './dependence.component';
@Component({
selector: 'app-service',
templateUrl: './service.component.html',
styleUrls: ['./service.component.scss'],
})
@Injectable()
export class ServiceComponent implements OnInit {
constructor() {
let injector = ReflectiveInjector.resolveAndCreate([Dependence]);
let dependence1 = injector.get(Dependence);
let dependence2 = injector.get(Dependence);
console.log('dependence1 === dependence2', dependence1 === dependence2); // true
}
ngOnInit() {}
}
複製代碼
在這裏咱們能夠看見打印出來的是true
,這裏咱們採用的是手動建立實例,因此咱們並不須要在providers中提供「菜譜」,實際上resolveAndCreate
的參數就是一個providers
ui
咱們有四種配置注入過程,即便用類、使用工廠、使用值、使用別名this
{provide: MyService, useClass: MyService}
複製代碼
這是咱們最多見的情形在angular中,一般若是provide的值和useclass的值同樣,咱們能夠簡化爲[MyService]
。
{provide: MyValue, useValue: 12345}
複製代碼
{provide: OldService, useClass: NewService}
複製代碼
若是咱們有兩個服務OldService
和NewService
接口都一致,出於某種緣由,咱們不得不使用OldService
做爲Token,可是咱們又想使用NewService
中的接口,那麼咱們就可使用別名。
[ NewLogger,
// Not aliased! Creates two instances of `NewLogger`
{ provide: OldLogger, useClass: NewLogger}]
複製代碼
這種狀況下會建立兩個NewLogger的實例,這顯然不是咱們想要的結果,這時咱們就可使用存在的
[ NewLogger,
// Alias OldLogger w/ reference to NewLogger
{ provide: OldLogger, useExisting: NewLogger}]
複製代碼
{provide: MyService, useFactory: (user: User) => {
user.isAdmin ? new adminService : customService,
deps: [User]
}}
複製代碼
當使用工廠時,咱們能夠經過變量的不一樣值,去實例不一樣的類。也就是說咱們須要根據不一樣的值返回不一樣的依賴實例的時候,那麼咱們就須要使用工廠。
目前爲止咱們的依賴都是存在的,可是實際狀況並非老是這樣。那麼咱們能夠經過@Optional裝飾器來解決這個問題。
import { Optional } from '@angular/core';
// ....
constructor(
@Optional() private dependenceService: DependenceService
) {}
複製代碼
可是這裏DependenceService這個服務類的定義仍是存在的,只是沒有準備好,例如沒有在providers中使用
依賴查找的規則是按照注入器從當前組件向父級組件查找,直到找到這個依賴爲止,可是若是限定查找路徑截止在宿主組件,那麼若是宿主組件中沒有就會報錯,咱們能夠經過@Host修飾器達到這一功能。
若是一個組件注入了依賴項,那麼這個組件就是這個依賴項的宿主組件,可是若是這個組件經過
ng-content
被嵌入到宿主組件,那麼這個宿主組件就是該依賴項的宿主組件。
當咱們在構造函數中使用private dependenceService: DependenceService
,injector就能夠正確的知道咱們要實例哪個類,這是由於在這裏DependenceService
充當了Token的角色(也就是說類名是能夠充當Token的),咱們只須要在providers中去尋找具備相同Token的值就行,可是每每咱們注入不是一個類,而是一個字符串,function或者對象。而這裏string、方法名和對象是不可以充當Token的,那麼這時咱們就須要來手動建立一個Token:
import { InjectionToken } from '@angular/core';
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
複製代碼
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
複製代碼
Inject 裝飾器顯示的聲明所依賴對象的類型
@Injectable()
class A {
constructor(private buffer: Buffer) {}
}
複製代碼
等同於
class A {
constructor(@Inject(Buffer) private buffer: Buffer) {}
}
複製代碼