Angular4學習之依賴注入

在一個項目中,組件和服務之間存在錯綜複雜的關係,爲了最小程度的耦合,咱們須要來管理組織這種關係,依賴注入就是管理這種關係的一種方式。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類依賴於EngineTires這兩個類,咱們在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的參數就是一個providersui

Providers

咱們有四種配置注入過程,即便用類、使用工廠、使用值、使用別名this

  • 使用類
{provide: MyService, useClass: MyService}
複製代碼

這是咱們最多見的情形在angular中,一般若是provide的值和useclass的值同樣,咱們能夠簡化爲[MyService]

  • 使用值 顯然並非每種狀況,咱們都須要注入一個類,有時候能夠僅僅是一個值
{provide: MyValue, useValue: 12345}
複製代碼
  • 使用別名
{provide: OldService, useClass: NewService}
複製代碼

若是咱們有兩個服務OldServiceNewService接口都一致,出於某種緣由,咱們不得不使用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]
}}
複製代碼

當使用工廠時,咱們能夠經過變量的不一樣值,去實例不一樣的類。也就是說咱們須要根據不一樣的值返回不一樣的依賴實例的時候,那麼咱們就須要使用工廠。

@Options 、@Host

目前爲止咱們的依賴都是存在的,可是實際狀況並非老是這樣。那麼咱們能夠經過@Optional裝飾器來解決這個問題。

import { Optional } from '@angular/core';
// ....
constructor(
    @Optional() private dependenceService: DependenceService
) {}
複製代碼

可是這裏DependenceService這個服務類的定義仍是存在的,只是沒有準備好,例如沒有在providers中使用

依賴查找的規則是按照注入器從當前組件向父級組件查找,直到找到這個依賴爲止,可是若是限定查找路徑截止在宿主組件,那麼若是宿主組件中沒有就會報錯,咱們能夠經過@Host修飾器達到這一功能。

若是一個組件注入了依賴項,那麼這個組件就是這個依賴項的宿主組件,可是若是這個組件經過ng-content被嵌入到宿主組件,那麼這個宿主組件就是該依賴項的宿主組件。

Token

當咱們在構造函數中使用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) {}
}
複製代碼

更過精彩

相關文章
相關標籤/搜索