angular2 的依賴注入包含了太多的內容,其中的一個重點就是注入器,而注入器又非常難理解,今天我們不深入介紹注入器的內容,可以參考官方文檔,我們今天來說注入器的層級。
也就是組件獲取服務的容器會選擇具體哪一個。
先簡單介紹一個背景:有3個組件AppComponent 根組件、DetailList組件 ( 日志列表組件)、Detail組件( 日志組件)。

這三個組件會形成一個組件樹,對應的我們也可以認為每個組件都會有一個獨立的注入器(有時候不會出現,但是可以這么認為)。
加入一個日志服務LoggerService,如果按照我們普通的入門方式,在根模塊providers 中提供LoggerService。那么在整個應用程序中,LoggerService只有一個實例,什么意思呢?就是說無論在哪個組件,獲取到的都是首次創建的LoggerService,所有組件共用一個服務實例,這有時候會是一個有用的特性,比如我們使用的全局配置。
全局唯一不是我們這次要驗證的重點,因為這個太普通,我們這次要說明的是我們如何在每個組件中都獲取單獨的LoggerService實例,即每個組件的實例都不同。這個就需要對ng2的依賴注入有所了解才可以。
我們逐步來說明如何實現?
為了便於看到這篇短文的同學有所了解,我加入一些基礎代碼。
1.app.module.ts 應用程序根模塊。注意此處我們沒有在Providers中注冊loggerService。當然注冊了通過后面的方法也可以達到我們的目的。
1 import { NgModule, Optional, SkipSelf, ReflectiveInjector} from '@angular/core'; 2 import { BrowserModule } from '@angular/platform-browser'; 3 4 /* App Root */ 5 import { AppComponent } from './app.component'; 6 import { routing } from './app.routing'; 7 import { Title } from '@angular/platform-browser'; 8 import {MessagesModule, GrowlModule, ButtonModule}from 'primeng/primeng'; 9 import {AppDetailComponent}from './app-detail.component'; 10 import {AppDetailListComponent}from './app-detailList.component'; 11 import {LoggerService}from './logger.service'; 12 let allTitle:string="郭志奇"; 13 14 @NgModule({ 15 imports: [ 16 BrowserModule, 17 MessagesModule, 18 GrowlModule, ButtonModule 19 ], 20 declarations: [AppComponent, AppDetailComponent, AppDetailListComponent],//聲明當前模塊需要的指定 組件信息 21 exports: [], 22 providers: [Title], 23 bootstrap: [AppComponent] 24 }) 25 export class AppModule { 26 constructor( @Optional() @SkipSelf() parentModule: AppModule) { 27 console.log(parentModule); 28 if (parentModule) { 29 throw new Error( 30 'AppModule is already loaded. Import it in the AppModule only'); 31 } 32 } 33 }
2.app.component.ts 應用程序根組件
1 import { Component, ViewEncapsulation, Host, ViewContainerRef, ReflectiveInjector } from '@angular/core'; 2 import { Title } from '@angular/platform-browser'; 3 import { Message } from 'primeng/primeng'; 4 import {LoggerService}from './logger.service'; 5 @Component({ 6 selector: 'my-app', 7 moduleId: module.id, 8 templateUrl: './app.component.html', 9 providers: [ 10 { provide: LoggerService, useClass: LoggerService } 11 ] 12 }) 13 export class AppComponent { 14 subtitle = '(Final)'; 15 private msgs: Message[]; 16 constructor(private title: Title, @Host() private logger: LoggerService) { 17 this.title.setTitle("AppComponent"); 18 } 19 20 show(): void { 21 this.logger.Debug(); 22 } 23 }
請注意,我們在跟組件中providers中注冊了LoggerService。
3.app.detailList.ts 日志列表中providers中也注冊了LoggerService
import {Component, Host}from '@angular/core';
import {LoggerService}from './logger.service';
@Component({
selector: 'my-detailList',
templateUrl: './app-detailList.component.html',
moduleId: module.id,
providers: [
{ provide: LoggerService, useClass: LoggerService }
]
})
export class AppDetailListComponent {
constructor( private logger: LoggerService) {
}
show(): void {
this.logger.Debug();
}
}
4.app.detail.ts 日志組件providers沒有注冊LoggerService。
1 import {Component, Host}from '@angular/core'; 2 import {LoggerService}from './logger.service'; 3 @Component({ 4 selector: 'detail', 5 moduleId: module.id, 6 templateUrl: './app-detail.component.html', 7 providers: [ 8 // { provide: LoggerService, useClass: LoggerService } 9 ] 10 }) 11 12 export class AppDetailComponent { 13 constructor( private logger: LoggerService) { 14 15 } 16 show(): void { 17 this.logger.Debug(); 18 } 19 20 }
現在我們通過chrome來看一下 LoggerService的層級關系。



通過查看依賴關系圖,我們可以看到AppComponent組件使用了單獨的LoggerService,DetailList組件也使用單獨的LoggerService 實例,而Detail組件使用的是父組件DetailList的LoggerService實例。
目前來看沒有達到我們的要求,我們的要求是每個組件都有單獨的LoggerService實例,那么我們假設Detail組件的providers是我們忘記輸入的,很難測試出原因所在。那么我們加入一個@Host()來限制注入器的查找范圍。
對於注入器的向上查找方式,請參考官方文檔。
為了便於調試,我們加入@Host().
@Host 裝飾器將把往上搜索的行為截止在 宿主組件
detail.ts 提示detail組件加入@Host()裝飾器
1 import {Component, Host}from '@angular/core'; 2 import {LoggerService}from './logger.service'; 3 @Component({ 4 selector: 'detail', 5 moduleId: module.id, 6 templateUrl: './app-detail.component.html', 7 providers: [ 8 // { provide: LoggerService, useClass: LoggerService } 9 ] 10 }) 11 12 export class AppDetailComponent { 13 constructor( @Host() private logger: LoggerService) { 14 15 } 16 show(): void { 17 this.logger.Debug(); 18 } 19 20 }
會提示找不到LoggerService的實例,@Host()的作用就是限制注入器查找到當前組件就停止,不會繼續往上查找。所以會出現找不到Providers的錯誤。
加上providers 的結果就是我們想要的了。

完美的解決了多組件使用單獨服務實例的問題。
總結:
1.如果要使組件單獨使用服務,那么首先要在providers 中單獨注冊該服務。很容易理解
2.為了更好的檢測可能出現的問題,在組件服務上加入@Host()裝飾器,可以盡量早的拋出錯誤信息
3.使用ng2的debug工具
4.要明確各組件之間的關系,因為不同的組件關系會導致服務的實例的不同
5.服務盡量是模塊級,不是應用級。
angular2,一個值得學習的東西。
