Angular Service注入器的作用範圍(ModuleInjector & ElementInjector)

Angular的元件,有些時候會把一些共用邏輯寫到Service裡面,但是會發現明明就是不同的元件,但不同元件之間的變數可能會被不同元件互相連動影響,導致變數有可能變成共用的這種情形發生。

  • 我們先做一個測試的計數器元件

count-button-service.service.ts

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

@Injectable({
  providedIn: 'root'
})
export class CountButtonServiceService {

  myCount:number = 0;

  constructor() { }

  PlusCount(): number{
    return this.myCount ++;
  }

  MinusCount(): number{
    return this.myCount --;
  }
}

count-button.component.ts

@Component({
  selector: 'my-count-button',
  templateUrl: './count-button.component.html',
  styleUrls: ['./count-button.component.scss']  
})

export class CountButtonComponent 
{
  myCount:number = 0;

  constructor(private _cbService: CountButtonServiceService){}

  Plus():number
  {
    this.myCount = this._cbService.PlusCount();
    return this.myCount;
  }

  Minus(): number{
    this.myCount = this._cbService.MinusCount();
    return this.myCount;
  }
}

count-button.component.html

<div class="d-inline p-2 bg-primary text-white" (click)="Plus();">plus</div>
<input type="text" [(ngModel)]="myCount">
<div class="d-inline p-2 bg-dark text-white" (click)="Minus();">minus</div>

結果大概如下


  • 接著來進行測試,

我們把計數器1先plus到10。新增一個Service的時候,預設式會從Root Module進行注入(通常是app.module),

接著再按一下計數器2的plus,發現計數器2居然不是從1開始,而是直接跳到11。這就是一開始提到的不同元件之間的變數被其他元件互相影響到的問題。


  • 知道問題後,我們來分析看看是什麼原因導致這個問題

首先,要先知道一個重要的東西:

  • injector:注入器。注入器又分為兩種
    • ModuleInjector模組注入器:透過@Injectable.providerIn或是@NgModule.providers陣列來進行配置
    • ElementInjector元素注入器:透過@Component的providers來進行配置

所以我們可以知道,當我們新增一個Service時,會透過@Injectable裝飾器,把Service實體註冊到AppModule的@NgModule(注入器)中

如果把@Injectable裝飾器註解掉的話。也可以直接把Service實體註冊到到AppModule的@NgModule(注入器)中

但不管是透過@Injectable或是@NgModule來註冊注入器,整個專案不論你是從哪裡注入,Angular 會依注入器為範圍來建立依賴實體,使用的都是共用一個Service實體(Singleton Service),以我們這個範例來看,注入器的範圍是整個AppModule,在AppModule底下的元件使用的都是同一個Service實體,所以才會有發生上述的連動影響問題。

https://medium.com/weekly-webtips/a-singleton-service-in-angular-a6ed577413d6

  • 如果不想要讓Service成為一個Singleton Service的話,可以把Service實體註冊到範圍比較小的注入器,也就是先前提到的ElementInjector,那這樣的話Service實體的範圍就只在一個元件(Component)裡面,就不會被其他的元件影響到了
註解掉NgModule注入器裡的providers配置
把注入器改為@Component,縮小注入器範圍為元件

  • 快速總結:
    @NgModule跟@Component都可視為是一個注入器,並可透過providers陣列屬性來設置要註冊的Service,Angular 會依注入器為範圍來建立Service的依賴實體
https://medium.com/weekly-webtips/a-singleton-service-in-angular-a6ed577413d6

  • 這兩天研究Angular的Service後,覺得Angular的DI其實跟.Net(Core)的DI其實很類似
    • 註冊的部分比較有些差異
      • .Net(Core):builder.Services.AddScoped<ILoggerService, LoggerService>()。AddScoped為Service實體的生命週期
      • Angular:providers: [{ provide: LoggerService, useClass: NewLoggerService}]。Service實體如果在@NgModule.providers註冊,則預設的生命週期為Singleton
    • 注入的方式幾乎一樣,都是採用建構子注入的方式

 

Ref:
1.Angular Custom Modal - 共用元件實作 - 彈窗 (1)
2.第 20 型 - 依賴注入 (Dependency Injection, DI) =>Service抽換可以參考這篇
3.A singleton service in Angular
4.快速理解 Angular Service:關於生命週期, providedIn root, lazy Module 那些事 => @Injectable.providerIn & @NgModule.provider的差別可以參考這篇,講解的很詳細
5.angular 依赖注入 多级注入器 => ModuleInjector & ElementInjector 的差異可以參考這篇