Angular2 之 依賴注入
依賴注入這部分分為兩部分來學習。第一部分自然是官網上的文件,另外一部分,是自己的“血淚時間史”。
之所以稱之為“血淚時間史”,是因為在這部分上花費的時間實在是有點多,也就是前面提到過的“時間的教訓”,所以在這裡要記錄下來,避免下次再犯這樣的錯誤。
依賴注入
依賴注入是一個用來管理程式碼依賴的強大模式。
應用程式全域性依賴
在這裡主要說的是,在應用程式根元件AppComponent中註冊那些被應用程式全域性使用的依賴提供商。
import { LoggerService } from './logger.service';
import { UserContextService } from './user-context.service' ;
import { UserService } from './user.service';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app.component.html',
providers: [ LoggerService, UserContextService, UserService ]
})
export class AppComponent {
/* . . . */
}
在@Component元資料的providers陣列匯入和註冊了幾個服務,這些服務都是使用類來實現的,所以服務類能充當自己的提供商。==> 淚在providers
我們在根元件中註冊了這些服務,所以我們可以在應用程式的任何地方,把它們注入到任何元件和服務的建構函式中去!
@Injectable()
- @Injectable裝飾器只在一個服務類有自己的依賴的時候,才是必不可少的。
@Injectable()
export class UserContextService {
constructor(private userService: UserService, private loggerService: LoggerService) {
}
}
這個@Injectable()就是必不可少的。
- AppComponent類有兩個依賴,但它沒有@Injectable()。 它不需要@Injectable(),這是因為元件類有@Component裝飾器。 在用TypeScript的Angular應用裡,有一個單獨的裝飾器 — 任何裝飾器 — 來標識依賴的型別就夠了。
把服務作用域限制到一個元件支樹
一個元件中注入的服務依賴,會在該元件的所有子元件中可見,而且Angular會把同樣的服務例項注入到需要該服務的子元件中。
這剛好在實際工作有遇到這樣的例子。
我在sino-base-data-service.component
元件中注入了BaseDataService
,那麼它的所有子元件sino-list.component
就都能訪問到這個service。
多個服務例項
主要講的是,在元件級別注入服務,每個元件就能擁有自己獨立的service例項。這樣就能保證每個元件都有自己的服務。每個元件都有自己的工作狀態,與其他元件的服務與狀態相隔離,這種我們成為沙盒化。每個服務和元件都在自己的沙盒裡執行。
@Optional 和 @Host
當元件申請一個依賴時,Angular從該元件本身的注入器開始,沿著依賴注入器的樹往上找,直到找到第一個符合要求的提供商。如果罩杯多就會丟擲一個錯誤。
@Optional
當Angular找不到依賴時,@Optional裝飾器會告訴Angular繼續執行,Angualr會把此注入引數設定為null(而不是預設的丟擲錯誤的行為)。
@Host
該裝飾器將把往上搜索的行為截止在宿主元件。
使用提供商來定義依賴
- useValue - 值提供商
- useClass
- useExisting
- useFactory
useValue
通常在單元測試中使用。useValue的值必須是立即定義的。
e.g.
{ provide: Hero, useValue: someHero },
{ provide: TITLE, useValue: 'Hero of the Month' },
useClass
useClasst提供商建立並返回一個指令類的新例項。
使用該技術來為公共或預設類提供備選實現。該替代品能實現一個不同的策略,比如拓展預設類或者在測試的時候假冒真實類。
{ provide: HeroService, useClass: HeroService },
{ provide: LoggerService, useClass: DateLoggerService },
useExisting - 別名-提供商
使用useExisting,提供商可以把一個令牌對映到另一個令牌上。實際上,第一個令牌是第二個令牌所對應的服務的一個別名,創造了訪問同一個服務物件的兩種方法。
{ provide: MinimalLogger, useExisting: LoggerService },
通過使用別名介面來把一個API變窄,是一個很重要的該技巧的使用例子.
useFactory - 工廠-提供商
useFactory提供商通過呼叫工廠函式來新建一個依賴物件。
{ provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] }
知識點
- 令牌 - 使用提供商來定義依賴
我們通常在建構函式裡面,為引數指定型別,讓Angular來處理依賴注入。該引數型別就是依賴注入器所需的令牌。 Angular把該令牌傳給注入器,然後把得到的結果賦給引數。
使用angular的DI系統來獲取引數時,必須將該令牌需要依賴的其他服務一併注入。
e.g.
要模擬BaseDataService
必須有ModuleConfig
export class CrudModule {
static forRoot(config: any, routeConfig?: any): ModuleWithProviders {
return {
ngModule: CrudModule,
providers: [
BaseDataService,
{
provide: ModuleConfig,
useValue: config,
}, {
provide: RouteConfig,
useValue: routeConfig ? routeConfig : DEFAULT_ROUTE_CONFIG,
},
],
};
}
}
@Injectable()
export class SinoListComponent implements OnInit {
constructor(
private config: ModuleConfig,
private baseDataService: BaseDataService,) {}
}
ng-content
把對應元件中的內容投影進元件的檢視中。
<ion-content>
<sino-loading-hint [state]="state" loadingTitle="loading..." needRefresh=true (onVoted)="onVoted($event)">
<h2 class="sino-list-title">{{title}}</h2>
<div class="sino-list" *ngFor="let item of datas; let i = index" (click)="toDetail(item, i)">
<h3>{{item.title}}</h3>
<p>
來自:{{item.ngDepartmentName}}的發文申請<br/>
<span>傳送時間:{{item.signDate}}</span>
</p>
</div>
</sino-loading-hint>
</ion-content>
“`
寫在後面
GitHub上集大家之力搞了一個前端面試題的專案,裡面都是大家面試時所遇到的題以及一些學習資料,有興趣的話可以關注一下。如果你也有興趣加入我們的話,請在專案中留言。專案同時也可以在gitbook上檢視。