Angular DI提供者

2022-07-01 13:57 更新

依賴提供者

通過配置提供者,你可以把服務(wù)提供給那些需要它們的應(yīng)用部件。

依賴提供者會(huì)使用 DI 令牌來配置注入器,注入器會(huì)用它來提供這個(gè)依賴值的具體的、運(yùn)行時(shí)版本。

指定提供者令牌

如果你把服務(wù)類指定為提供者令牌,那么注入器的默認(rèn)行為是用 ?new ?來實(shí)例化那個(gè)類。

在下面這個(gè)例子中,?Logger ?類提供了 ?Logger ?的實(shí)例。

providers: [Logger]

不過,你也可以用一個(gè)替代提供者來配置注入器,這樣就可以指定另一些同樣能提供日志功能的對(duì)象。

可以使用服務(wù)類來配置注入器,也可以提供一個(gè)替代類、一個(gè)對(duì)象或一個(gè)工廠函數(shù)。

依賴注入令牌

當(dāng)使用提供者配置注入器時(shí),會(huì)將該提供者與依賴項(xiàng)注入令牌(或叫 DI 令牌)關(guān)聯(lián)起來。注入器允許 Angular 創(chuàng)建任何內(nèi)部依賴項(xiàng)的映射。DI 令牌會(huì)充當(dāng)該映射的鍵名。

依賴項(xiàng)值是一個(gè)實(shí)例,而這個(gè)類的類型用作查找鍵。在這里,注入器使用 ?HeroService ?類型作為令牌來查找 ?heroService?。

heroService: HeroService;

當(dāng)你使用 ?HeroService ?類的類型來定義構(gòu)造函數(shù)參數(shù)時(shí),Angular 會(huì)注入與這個(gè) ?HeroService ?類令牌相關(guān)聯(lián)的服務(wù):

constructor(heroService: HeroService)

盡管許多依賴項(xiàng)的值是通過類提供的,但擴(kuò)展的 ?provide ?對(duì)象使你可以將不同種類的提供者與 DI 令牌相關(guān)聯(lián)。

定義提供者

類提供者的語法實(shí)際上是一種簡(jiǎn)寫形式,它會(huì)擴(kuò)展成一個(gè)由 ?Provider ?接口定義的提供者配置對(duì)象。 下面的代碼片段展示了 ?providers ?中給出的類會(huì)如何擴(kuò)展成完整的提供者配置對(duì)象。

providers: [Logger]

Angular 把這個(gè) ?providers ?值擴(kuò)展為一個(gè)完整的提供者對(duì)象,如下所示。

[{ provide: Logger, useClass: Logger }]

擴(kuò)展的提供者配置是一個(gè)具有兩個(gè)屬性的對(duì)象字面量:

  • ?provide ?屬性存有令牌,它作為一個(gè) key,在定位依賴值和配置注入器時(shí)使用。
  • 第二個(gè)屬性是一個(gè)提供者定義對(duì)象,它告訴注入器要如何創(chuàng)建依賴值。 提供者定義對(duì)象中的 key 可以是 ?useClass ?—— 就像這個(gè)例子中一樣。 也可以是 ?useExisting?、?useValue ?或 ?useFactory?。 每一個(gè) key 都用于提供一種不同類型的依賴,我們稍后會(huì)討論。

指定替代性的類提供者

不同的類可以提供相同的服務(wù)。例如,以下代碼告訴注入器,當(dāng)組件使用 ?Logger ?令牌請(qǐng)求一個(gè) logger 時(shí),給它返回一個(gè) ?BetterLogger?。

[{ provide: Logger, useClass: BetterLogger }]

配置帶依賴的類提供者

如果替代類提供者有自己的依賴,那就在父模塊或組件的元數(shù)據(jù)屬性 ?providers ?中指定那些依賴。

[ UserService,
  { provide: Logger, useClass: EvenBetterLogger }]

在這個(gè)例子中,?EvenBetterLogger ?會(huì)在日志信息里顯示用戶名。 這個(gè) logger 要從注入的 ?UserService ?實(shí)例中來獲取該用戶。

@Injectable()
export class EvenBetterLogger extends Logger {
  constructor(private userService: UserService) { super(); }

  override log(message: string) {
    const name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}

注入器需要提供這個(gè)新的日志服務(wù)以及該服務(wù)所依賴的 ?UserService ?對(duì)象。

別名類提供者

要為類提供者設(shè)置別名,請(qǐng)?jiān)?nbsp;?providers ?數(shù)組中使用 ?useExisting ?屬性指定別名和類提供者。

在下面的例子中,當(dāng)組件請(qǐng)求新的或舊的記錄器時(shí),注入器都會(huì)注入一個(gè) ?NewLogger ?的實(shí)例。 通過這種方式,?OldLogger ?就成了 ?NewLogger ?的別名。

[ NewLogger,
  // Alias OldLogger w/ reference to NewLogger
  { provide: OldLogger, useExisting: NewLogger}]

請(qǐng)確保你沒有使用 ?useClass ?來把 ?OldLogger ?設(shè)為 ?NewLogger ?的別名,因?yàn)槿绻@樣做它就會(huì)創(chuàng)建兩個(gè)不同的 ?NewLogger ?實(shí)例。

為類接口指定別名

通常,編寫同一個(gè)父組件別名提供者的變體時(shí)會(huì)使用forwardRef,如下所示。

providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],

為簡(jiǎn)化你的代碼,可以使用輔助函數(shù) ?provideParent()? 來把這個(gè)邏輯提取到一個(gè)輔助函數(shù)中。

// Helper method to provide the current component instance in the name of a `parentType`.
export function provideParent
  (component: any) {
    return { provide: Parent, useExisting: forwardRef(() => component) };
  }

現(xiàn)在,你可以為組件添加一個(gè)更容易閱讀和理解的父提供者。

providers:  [ provideParent(AliceComponent) ]

為多個(gè)類接口指定別名

要為多個(gè)父類型指定別名(每個(gè)類型都有自己的類接口令牌),請(qǐng)配置 ?provideParent()? 以接受更多的參數(shù)。

這是一個(gè)修訂版本,默認(rèn)值為 ?parent ?但同時(shí)也接受另一個(gè)父類接口作為可選的第二參數(shù)。

// Helper method to provide the current component instance in the name of a `parentType`.
// The `parentType` defaults to `Parent` when omitting the second parameter.
export function provideParent
  (component: any, parentType?: any) {
    return { provide: parentType || Parent, useExisting: forwardRef(() => component) };
  }

接下來,要使用 ?provideParent()?,請(qǐng)傳入第二參數(shù),這里是 ?DifferentParent?。

providers:  [ provideParent(BethComponent, DifferentParent) ]

注入一個(gè)對(duì)象

要注入一個(gè)對(duì)象,可以用 ?useValue ?選項(xiàng)來配置注入器。 下面的提供者定義對(duì)象使用 ?useValue ?作為 key 來把該變量與 ?Logger ?令牌關(guān)聯(lián)起來。

[{ provide: Logger, useValue: SilentLogger }]

在這個(gè)例子中,?SilentLogger ?是一個(gè)充當(dāng)記錄器角色的對(duì)象。

// An object in the shape of the logger service
function silentLoggerFn() {}

export const SilentLogger = {
  logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
  log: silentLoggerFn
};

注入一個(gè)配置對(duì)象

常用的對(duì)象字面量是配置對(duì)象。下列配置對(duì)象包括應(yīng)用的標(biāo)題和 Web API 的端點(diǎn)地址。

export const HERO_DI_CONFIG: AppConfig = {
  apiEndpoint: 'api.heroes.com',
  title: 'Dependency Injection'
};

要提供并注入配置對(duì)象,請(qǐng)?jiān)?nbsp;?@NgModule()? 的 ?providers ?數(shù)組中指定該對(duì)象。

providers: [
  UserService,
  { provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
],

使用 InjectionToken 對(duì)象

可以定義和使用一個(gè) ?InjectionToken ?對(duì)象來為非類的依賴選擇一個(gè)提供者令牌。下列例子定義了一個(gè)類型為 ?InjectionToken ?的 ?APP_CONFIG ?。

import { InjectionToken } from '@angular/core';

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

可選的參數(shù) ?<AppConfig>? 和令牌描述 ?app.config? 指明了此令牌的用途。

接著,用 ?APP_CONFIG ?這個(gè) ?InjectionToken ?對(duì)象在組件中注冊(cè)依賴提供者。

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

現(xiàn)在,借助參數(shù)裝飾器 ?@Inject()?,你可以把這個(gè)配置對(duì)象注入到構(gòu)造函數(shù)中。

constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

接口和依賴注入

雖然 TypeScript 的 ?AppConfig ?接口可以在類中提供類型支持,但它在依賴注入時(shí)卻沒有任何作用。在 TypeScript 中,接口是一項(xiàng)設(shè)計(jì)期工件,它沒有可供 DI 框架使用的運(yùn)行時(shí)表示形式或令牌。

當(dāng)轉(zhuǎn)譯器把 TypeScript 轉(zhuǎn)換成 JavaScript 時(shí),接口就會(huì)消失,因?yàn)?nbsp;JavaScript 沒有接口。

由于 Angular 在運(yùn)行期沒有接口,所以該接口不能作為令牌,也不能注入它。

// Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
// Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }

使用工廠提供者

要想根據(jù)運(yùn)行前尚不可用的信息創(chuàng)建可變的依賴值,可以使用工廠提供者。

在下面的例子中,只有授權(quán)用戶才能看到 ?HeroService ?中的秘密英雄。授權(quán)可能在單個(gè)應(yīng)用會(huì)話期間發(fā)生變化,比如改用其他用戶登錄。

要想在 ?UserService ?和 ?HeroService ?中保存敏感信息,就要給 ?HeroService ?的構(gòu)造函數(shù)傳一個(gè)邏輯標(biāo)志來控制秘密英雄的顯示。

constructor(
  private logger: Logger,
  private isAuthorized: boolean) { }

getHeroes() {
  const auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
  this.logger.log(`Getting heroes for ${auth} user.`);
  return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}

要實(shí)現(xiàn) ?isAuthorized ?標(biāo)志,可以用工廠提供者來為 ?HeroService ?創(chuàng)建一個(gè)新的 logger 實(shí)例。

const heroServiceFactory = (logger: Logger, userService: UserService) =>
  new HeroService(logger, userService.user.isAuthorized);

這個(gè)工廠函數(shù)可以訪問 ?UserService?。你可以同時(shí)把 ?Logger ?和 ?UserService ?注入到工廠提供者中,這樣注入器就可以把它們傳給工廠函數(shù)了。

export const heroServiceProvider =
  { provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  };
  • ?useFactory ?字段指定該提供者是一個(gè)工廠函數(shù),其實(shí)現(xiàn)代碼是 ?heroServiceFactory?。
  • ?deps ?屬性是一個(gè)提供者令牌數(shù)組。 ?Logger ?和 ?UserService ?類都是自己類提供者的令牌。該注入器解析了這些令牌,并把相應(yīng)的服務(wù)注入到 ?heroServiceFactory ?工廠函數(shù)的參數(shù)中。

通過把工廠提供者導(dǎo)出為變量 ?heroServiceProvider?,就能讓工廠提供者變得可復(fù)用。

下面這兩個(gè)并排的例子展示了在 ?providers ?數(shù)組中,如何用 ?heroServiceProvider ?替換 ?HeroService?

  • src/app/heroes/heroes.component (v3)
  • 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>
      `
    })
    export class HeroesComponent { }
  • src/app/heroes/heroes.component (v2)
  • import { Component } from '@angular/core';
    
    import { HeroService } from './hero.service';
    
    @Component({
      selector: 'app-heroes',
      providers: [ HeroService ],
      template: `
        <h2>Heroes</h2>
        <app-hero-list></app-hero-list>
      `
    })
    export class HeroesComponent { }


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)