真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Angular中如何使用依賴注入

這篇文章主要介紹了Angular中如何使用依賴注入,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

成都創(chuàng)新互聯(lián)公司是一家專注于網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)與策劃設(shè)計(jì),鶴山網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:鶴山等地區(qū)。鶴山做網(wǎng)站價(jià)格咨詢:18980820575

useFactory、useClass、useValue 和 useExisting 不同類型provider的應(yīng)用場(chǎng)景

下面,我們通過(guò)實(shí)際例子,來(lái)對(duì)幾個(gè)提供商的使用場(chǎng)景進(jìn)行說(shuō)明。

useFactory 工廠提供商

某天,咱們接到一個(gè)需求:實(shí)現(xiàn)一個(gè)本地存儲(chǔ)的功能,并將其注入Angular應(yīng)用中,使其可以在系統(tǒng)中全局使用

首先編寫服務(wù)類storage.service.ts,實(shí)現(xiàn)其存儲(chǔ)功能

// storage.service.ts
export class StorageService {
  get(key: string) {
    return JSON.parse(localStorage.getItem(key) || '{}') || {};
  }

  set(key: string, value: ITokenModel | null): boolean {
    localStorage.setItem(key, JSON.stringify(value));
    return true;
  }

  remove(key: string) {
    localStorage.removeItem(key);
  }
}

如果你馬上在user.component.ts中嘗試使用

// user.component.ts
@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class CourseCardComponent  {
  constructor(private storageService: StorageService) {
    ...
  }
  ...
}

你應(yīng)該會(huì)看到這樣的一個(gè)錯(cuò)誤:

NullInjectorError: No provider for StorageService!

顯而易見(jiàn),我們并沒(méi)有將StorageService添加到 Angular的依賴注入系統(tǒng)中。Angular無(wú)法獲取StorageService依賴項(xiàng)的Provider,也就無(wú)法實(shí)例化這個(gè)類,更沒(méi)法調(diào)用類中的方法。

接下來(lái),我們本著缺撒補(bǔ)撒的理念,手動(dòng)添加一個(gè)Provider。修改storage.service.ts文件如下

// storage.service.ts
export class StorageService {
  get(key: string) {
    return JSON.parse(localStorage.getItem(key) || '{}') || {};
  }

  set(key: string, value: any) {
    localStorage.setItem(key, JSON.stringify(value));
  }

  remove(key: string) {
    localStorage.removeItem(key);
  }
}

// 添加工廠函數(shù),實(shí)例化StorageService
export storageServiceProviderFactory(): StorageService {
  return new StorageService();
}

通過(guò)上述代碼,我們已經(jīng)有了Provider。那么接下來(lái)的問(wèn)題,就是如果讓Angular每次掃描到StorageService這個(gè)依賴項(xiàng)的時(shí)候,讓其去執(zhí)行storageServiceProviderFactory方法,來(lái)創(chuàng)建實(shí)例

這就引出來(lái)了下一個(gè)知識(shí)點(diǎn)InjectionToken

在一個(gè)服務(wù)類中,我們常常需要添加多個(gè)依賴項(xiàng),來(lái)保證服務(wù)的可用。而InjectionToken是各個(gè)依賴項(xiàng)的唯一標(biāo)識(shí),它讓Angular的依賴注入系統(tǒng)能準(zhǔn)確的找到各個(gè)依賴項(xiàng)的Provider。

接下來(lái),我們手動(dòng)添加一個(gè)InjectionToken

// storage.service.ts
import { InjectionToken } from '@angular/core';

export class StorageService {
  get(key: string) {
    return JSON.parse(localStorage.getItem(key) || '{}') || {};
  }

  set(key: string, value: any) {
    localStorage.setItem(key, JSON.stringify(value));
  }

  remove(key: string) {
    localStorage.removeItem(key);
  }
}

export storageServiceProviderFactory(): StorageService {
  return new StorageService();
}

// 添加StorageServiced的InjectionToken
export const STORAGE_SERVICE_TOKEN = new InjectionToken('AUTH_STORE_TOKEN');

ok,我們已經(jīng)有了StorageServiceProviderInjectionToken。

接下來(lái),我們需要一個(gè)配置,讓Angular依賴注入系統(tǒng)能夠?qū)ζ溥M(jìn)行識(shí)別,在掃描到StorageService(Dependency)的時(shí)候,根據(jù)STORAGE_SERVICE_TOKEN(InjectionToken)去找到對(duì)應(yīng)的storageServiceProviderFactory(Provider),然后創(chuàng)建這個(gè)依賴項(xiàng)的實(shí)例。如下,我們?cè)?code>module中的@NgModule()裝飾器中進(jìn)行配置:

// user.module.ts
@NgModule({
  imports: [
    ...
  ],
  declarations: [
    ...
  ],
  providers: [
    {
      provide: STORAGE_SERVICE_TOKEN, // 與依賴項(xiàng)關(guān)聯(lián)的InjectionToken,用于控制工廠函數(shù)的調(diào)用
      useFactory: storageServiceProviderFactory, // 當(dāng)需要?jiǎng)?chuàng)建并注入依賴項(xiàng)時(shí),調(diào)用該工廠函數(shù)
      deps: [] // 如果StorageService還有其他依賴項(xiàng),這里添加
    }
  ]
})
export class UserModule { }

到這里,我們完成了依賴的實(shí)現(xiàn)。最后,還需要讓Angular知道在哪里注入Angular提供了 @Inject裝飾器來(lái)識(shí)別

// user.component.ts
@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class CourseCardComponent  {
  constructor(@Inject(STORAGE_SERVICE_TOKEN) private storageService: StorageService) {
    ...
  }
  ...
}

到此,我們便可以在user.component.ts調(diào)用StorageService里面的方法了

useClass 類提供商

emm...你是否覺(jué)得上述的寫法過(guò)于復(fù)雜了,而在實(shí)際開發(fā)中,我們大多數(shù)場(chǎng)景是無(wú)需手動(dòng)創(chuàng)建ProviderInjectionToken的。如下:

// user.component.ts
@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class CourseCardComponent  {
  constructor(private storageService: StorageService) {
    ...
  }
  ...
}

// storage.service.ts
@Injectable({ providedIn: 'root' })
export class StorageService {}

// user.module.ts
@NgModule({
  imports: [
    ...
  ],
  declarations: [
    ...
  ],
  providers: [StorageService]
})
export class UserModule { }

下面,我們來(lái)對(duì)上述的這種簡(jiǎn)寫模式進(jìn)行剖析。

user.component.ts,我們舍棄了@Inject裝飾器,直接添加依賴項(xiàng)private storageService: StorageService,這得益于Angular對(duì)InjectionToken的設(shè)計(jì)。

InjectionToken不一定必須是一個(gè)InjectionToken object,只要保證它在運(yùn)行時(shí)環(huán)境中能夠識(shí)別對(duì)應(yīng)的唯一依賴項(xiàng)即可。所以,在這里,你可以用類名即運(yùn)行時(shí)中的構(gòu)造函數(shù)名稱來(lái)作為依賴項(xiàng)InjectionToken。省略創(chuàng)建InjectionToken這一步驟。

// user.module.ts
@NgModule({
  imports: [
    ...
  ],
  declarations: [
    ...
  ],
  providers: [{
    provide: StorageService, // 使用構(gòu)造函數(shù)名作為InjectionToken
    useFactory: storageServiceProviderFactory,
    deps: []
  }]
})
export class UserModule { }

注意:由于Angular依賴注入系統(tǒng)是在運(yùn)行時(shí)環(huán)境中根據(jù)InjectionToken識(shí)別依賴項(xiàng),進(jìn)行依賴注入的。所以這里不能使用interface名稱作為InjectionToken,因?yàn)槠渲淮嬖谟?code>Typescript語(yǔ)言的編譯期,并不存在于運(yùn)行時(shí)中。而對(duì)于類名來(lái)說(shuō),其在運(yùn)行時(shí)環(huán)境中以構(gòu)造函數(shù)名體現(xiàn),可以使用。

接下來(lái),我們可以使用useClass替換useFactory,其實(shí)也能達(dá)到創(chuàng)建實(shí)例的效果,如下:

...
providers: [{
  provide: StorageService,
  useClass: StorageService,
  deps: []
}]
...

當(dāng)使用useClass時(shí),Angular會(huì)將后面的值當(dāng)作一個(gè)構(gòu)造函數(shù),在運(yùn)行時(shí)環(huán)境中,直接執(zhí)行new指令進(jìn)行實(shí)例化,這也無(wú)需我們?cè)偈謩?dòng)創(chuàng)建 Provider

當(dāng)然,基于Angular依賴注入設(shè)計(jì),我們可以寫得更簡(jiǎn)單

...
providers: [StorageService]
...

直接寫入類名providers數(shù)組中,Angular會(huì)識(shí)別其是一個(gè)構(gòu)造函數(shù),然后檢查函數(shù)內(nèi)部,創(chuàng)建一個(gè)工廠函數(shù)去查找其構(gòu)造函數(shù)中的依賴項(xiàng),最后再實(shí)例化

useClass還有一個(gè)特性是,Angular會(huì)根據(jù)依賴項(xiàng)Typescript中的類型定義,作為其運(yùn)行時(shí)InjectionToken去自動(dòng)查找Provider。所以,我們也無(wú)需使用@Inject裝飾器來(lái)告訴Angular在哪里注入了

你可以簡(jiǎn)寫如下

  ...
  // 無(wú)需手動(dòng)注入 :constructor(@Inject(StorageService) private storageService: StorageService)
  constructor(private storageService: StorageService) {
    ...
  }
  ...

這也就是我們平常開發(fā)中,最常見(jiàn)的一種寫法。

useValue 值提供商

完成本地存儲(chǔ)服務(wù)的實(shí)現(xiàn)后,我們又收到了一個(gè)新需求,研發(fā)老大希望提供一個(gè)配置文件,來(lái)存儲(chǔ)StorageService的一些默認(rèn)行為

我們先創(chuàng)建一個(gè)配置

const storageConfig = {
  suffix: 'app_' // 添加一個(gè)存儲(chǔ)key的前綴
  expires: 24 * 3600 * 100 // 過(guò)期時(shí)間,毫秒戳
}

useValue則可以 cover 住這種場(chǎng)景。其可以是一個(gè)普通變量,也可以是一個(gè)對(duì)象形式。

配置方法如下:

// config.ts
export interface STORAGE_CONFIG = {
  suffix: string;
  expires: number;
}

export const STORAGE_CONFIG_TOKEN = new InjectionToken('storage-config');
export const storageConfig = {
  suffix: 'app_' // 添加一個(gè)存儲(chǔ)key的前綴
  expires: 24 * 3600 * 100 // 過(guò)期時(shí)間,毫秒戳
}

// user.module.ts
@NgModule({
  ...
  providers: [
    StorageService,
    {
      provide: STORAGE_CONFIG_TOKEN,
      useValue: storageConfig
    }
  ],
  ...
})
export class UserModule {}

user.component.ts組件中,直接使用配置對(duì)象

// user.component.ts
@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class CourseCardComponent  {
  constructor(private storageService: StorageService, @Inject(STORAGE_CONFIG_TOKEN) private storageConfig: StorageConfig) {
    ...
  }

  getKey(): void {
    const { suffix } = this.storageConfig;
    console.log(this.storageService.get(suffix + 'demo'));
  }
}
useExisting 別名提供商

如果我們需要基于一個(gè)已存在的provider來(lái)創(chuàng)建一個(gè)新的provider,或需要重命名一個(gè)已存在的provider時(shí),可以用useExisting屬性來(lái)處理。比如:創(chuàng)建一個(gè)angular的表單控件,其在一個(gè)表單中會(huì)存在多個(gè),每個(gè)表單控件存儲(chǔ)不同的值。我們可以基于已有的表單控件provider來(lái)創(chuàng)建

// new-input.component.ts
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'new-input',
  exportAs: 'newInput',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => NewInputComponent), // 這里的NewInputComponent已經(jīng)聲明了,但還沒(méi)有被定義。無(wú)法直接使用,使用forwardRef可以創(chuàng)建一個(gè)間接引用,Angular在后續(xù)在解析該引用
      multi: true
    }
  ]
})
export class NewInputComponent implements ControlValueAccessor {
  ...
}

ModuleInjector 和 ElementInjector 層級(jí)注入器的意義

Angular中有兩個(gè)注入器層次結(jié)構(gòu)

  • ModuleInjector —— 使用 @NgModule() 或 @Injectable() 的方式在模塊中注入

  • ElementInjector —— 在 @Directive() 或 @Component() 的 providers 屬性中進(jìn)行配置

我們通過(guò)一個(gè)實(shí)際例子來(lái)解釋兩種注入器的應(yīng)用場(chǎng)景,比如:設(shè)計(jì)一個(gè)展示用戶信息的卡片組件

ModuleInjector 模塊注入器

我們使用user-card.component.ts來(lái)顯示組件,用UserService來(lái)存取該用戶的信息

// user-card.component.ts
@Component({
  selector: 'user-card.component.ts',
  templateUrl: './user-card.component.html',
  styleUrls: ['./user-card.component.less']
})
export class UserCardComponent {
  ...
}

// user.service.ts
@Injectable({
  providedIn: "root"
})
export class UserService {
  ...
}

上述代碼是通過(guò)@Injectable添加到根模塊中,root即根模塊的別名。其等價(jià)于下面的代碼

// user.service.ts
export class UserService {
  ...
}

// app.module.ts
@NgModule({
  ...
  providers: [UserService], // 通過(guò)providers添加
})
export class AppModule {}

當(dāng)然,如果你覺(jué)得UserService只會(huì)在UserModule模塊下使用的話,你大可不必將其添加到根模塊中,添加到所在模塊即可

// user.service.ts
@Injectable({
  providedIn: UserModule
})
export class UserService {
  ...
}

如果你足夠細(xì)心,會(huì)發(fā)現(xiàn)上述例子中,我們既可以通過(guò)在當(dāng)前service文件中的@Injectable({ provideIn: xxx })定義provider,也可以在其所屬module中的@NgModule({ providers: [xxx] })定義。那么,他們有什么區(qū)別呢?

@Injectable()@NgModule()除了使用方式不同外,還有一個(gè)很大的區(qū)別是:

使用 @Injectable() 的 providedIn 屬性優(yōu)于 @NgModule() 的 providers 數(shù)組,因?yàn)槭褂?@Injectable() 的 providedIn 時(shí),優(yōu)化工具可以進(jìn)行搖樹優(yōu)化 Tree Shaking,從而刪除你的應(yīng)用程序中未使用的服務(wù),以減小捆綁包尺寸。

我們通過(guò)一個(gè)例子來(lái)解釋上面的概述。隨著業(yè)務(wù)的增長(zhǎng),我們擴(kuò)展了UserService1UserService2兩個(gè)服務(wù),但由于某些原因,UserService2一直未被使用。

如果通過(guò)@NgModule()providers引入依賴項(xiàng),我們需要在user.module.ts文件中引入對(duì)應(yīng)的user1.service.tsuser2.service.ts文件,然后在providers數(shù)組中添加UserService1UserService2引用。而由于UserService2所在文件在module文件中被引用,導(dǎo)致Angular中的tree shaker錯(cuò)誤的認(rèn)為這個(gè)UserService2已經(jīng)被使用了。無(wú)法進(jìn)行搖樹優(yōu)化。代碼示例如下:

// user.module.ts
import UserService1 from './user1.service.ts';
import UserService2 from './user2.service.ts';
@NgModule({
  ...
  providers: [UserService1, UserService2], // 通過(guò)providers添加
})
export class UserModule {}

那么,如果通過(guò)@Injectable({providedIn: UserModule})這種方式,我們實(shí)際是在服務(wù)類自身文件中引用了use.module.ts,并為其定義了一個(gè)provider。無(wú)需在UserModule中在重復(fù)定義,也就不需要在引入user2.service.ts文件了。所以,當(dāng)UserService2沒(méi)有被依賴時(shí),即可被優(yōu)化掉。代碼示例如下:

// user2.service.ts
import UserModule from './user.module.ts';
@Injectable({
  providedIn: UserModule
})
export class UserService2 {
  ...
}
ElementInjector 組件注入器

在了解完ModuleInjector后,我們繼續(xù)通過(guò)剛才的例子講述ElementInjector。

最初,我們系統(tǒng)中的用戶只有一個(gè),我們也只需要一個(gè)組件和一個(gè)UserService來(lái)存取這個(gè)用戶的信息即可

// user-card.component.ts
@Component({
  selector: 'user-card.component.ts',
  templateUrl: './user-card.component.html',
  styleUrls: ['./user-card.component.less']
})
export class UserCardComponent {
  ...
}

// user.service.ts
@Injectable({
  providedIn: "root"
})
export class UserService {
  ...
}

注意:上述代碼將UserService被添加到根模塊中,它僅會(huì)被實(shí)例化一次。

如果這時(shí)候系統(tǒng)中有多個(gè)用戶,每個(gè)用戶卡片組件里的UserService需存取對(duì)應(yīng)用戶的信息。如果還是按照上述的方法,UserService只會(huì)生成一個(gè)實(shí)例。那么就可能出現(xiàn),張三存了數(shù)據(jù)后,李四去取數(shù)據(jù),取到的是張三的結(jié)果。

那么,我們有辦法實(shí)例化多個(gè)UserService,讓每個(gè)用戶的數(shù)據(jù)存取操作隔離開么?

答案是有的。我們需要在user.component.ts文件中使用ElementInjector,將UserServiceprovider添加即可。如下:

// user-card.component.ts
@Component({
  selector: 'user-card.component.ts',
  templateUrl: './user-card.component.html',
  styleUrls: ['./user-card.component.less'],
  providers: [UserService]
})
export class UserCardComponent {
  ...
}

通過(guò)上述代碼,每個(gè)用戶卡片組件都會(huì)實(shí)例化一個(gè)UserService,來(lái)存取各自的用戶信息。

如果要解釋上述的現(xiàn)象,就需要說(shuō)到AngularComponents and Module Hierarchical Dependency Injection。

在組件中使用依賴項(xiàng)時(shí),Angular會(huì)優(yōu)先在該組件的providers中尋找,判斷該依賴項(xiàng)是否有匹配的provider。如果有,則直接實(shí)例化。如果沒(méi)有,則查找父組件的providers,如果還是沒(méi)有,則繼續(xù)找父級(jí)的父級(jí),直到根組件(app.component.ts)。如果在根組件中找到了匹配的provider,會(huì)先判斷其是否有存在的實(shí)例,如果有,則直接返回該實(shí)例。如果沒(méi)有,則執(zhí)行實(shí)例化操作。如果根組件仍未找到,則開始從原組件所在的module開始查找,如果原組件所在module不存在,則繼續(xù)查找父級(jí)module,直到根模塊(app.module.ts)。最后,仍未找到則報(bào)錯(cuò)No provider for xxx

@Optional()、@Self()、@SkipSelf()、@Host() 修飾符的使用

Angular應(yīng)用中,當(dāng)依賴項(xiàng)尋找provider時(shí),我們可以通過(guò)一些修飾符來(lái)對(duì)搜索結(jié)果進(jìn)行容錯(cuò)處理限制搜索的范圍。

@Optional()

通過(guò)@Optional()裝飾服務(wù),表明讓該服務(wù)可選。即如果在程序中,沒(méi)有找到服務(wù)匹配的provider,也不會(huì)程序崩潰,報(bào)錯(cuò)No provider for xxx,而是返回null

export class UserCardComponent {
  constructor(@Optional private userService: UserService) {}
}
@Self()

使用@Self()Angular僅查看當(dāng)前組件或指令的ElementInjector。

如下,Angular只會(huì)在當(dāng)前UserCardComponentproviders中搜索匹配的provider,如果未匹配,則直接報(bào)錯(cuò)。No provider for UserService

// user-card.component.ts
@Component({
  selector: 'user-card.component.ts',
  templateUrl: './user-card.component.html',
  styleUrls: ['./user-card.component.less'],
  providers: [UserService],
})
export class UserCardComponent {
  constructor(@Self() private userService?: UserService) {}
}
@SkipSelf()

@SkipSelf()@Self()相反。使用@SkipSelf(),Angular在父ElementInjector中而不是當(dāng)前ElementInjector中開始搜索服務(wù).

// 子組件 user-card.component.ts
@Component({
  selector: 'user-card.component.ts',
  templateUrl: './user-card.component.html',
  styleUrls: ['./user-card.component.less'],
  providers: [UserService], // not work
})
export class UserCardComponent {
  constructor(@SkipSelf() private userService?: UserService) {}
}

// 父組件 parent-card.component.ts
@Component({
  selector: 'parent-card.component.ts',
  templateUrl: './parent-card.component.html',
  styleUrls: ['./parent-card.component.less'],
  providers: [
    {
      provide: UserService,
      useClass: ParentUserService, // work
    },
  ],
})
export class ParentCardComponent {
  constructor() {}
}
@Host()

@Host()使你可以在搜索provider時(shí)將當(dāng)前組件指定為注入器樹的最后一站。這和@Self()類似,即使樹的更上級(jí)有一個(gè)服務(wù)實(shí)例,Angular也不會(huì)繼續(xù)尋找。

multi 多服務(wù)提供商

某些場(chǎng)景下,我們需要一個(gè)InjectionToken初始化多個(gè)provider。比如:在使用攔截器的時(shí)候,我們希望在default.interceptor.ts之前添加一個(gè) 用于 token 校驗(yàn)的JWTInterceptor

...
const NET_PROVIDES = [
  { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true }
];
...

multi: 為false時(shí),provider的值會(huì)被覆蓋;設(shè)置為true,將生成多個(gè)provider并與唯一InjectionToken HTTP_INTERCEPTORS關(guān)聯(lián)。最后可以通過(guò)HTTP_INTERCEPTORS獲取所有provider的值

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Angular中如何使用依賴注入”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!


網(wǎng)頁(yè)標(biāo)題:Angular中如何使用依賴注入
文章URL:http://weahome.cn/article/jpchpc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部