angular2的依賴注入

更好閱讀體驗,請看原文css

在讀這篇文章以前,你要先了解一下什麼是依賴注入,網上關於這個的解釋不少,你們能夠自行Google.html

 

咱們這一篇文章仍是以QuickStart項目爲基礎,從頭開始講解怎麼在Angular2中使用依賴注入,若是你按照本篇文章中講解的示例親自走一遍的話,你必定可以掌握如何在Angular2中使用依賴注入.好,廢話很少說,開始咱們今天的旅行吧!java

咱們首先將項目中的內聯模板替換爲一個模板文件,使用templateUrl替換template:python

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>', templateUrl: 'app/templates/app.html' })

接下來咱們給本身的頁面添加一些展現的數據,咱們首先新建一個文件app/classes/User.ts,用來建立咱們的User實例:git

export class User{ constructor( private name:string, private age:number, private email:string ){} }

而後咱們在組件中引入這個類,而後建立咱們的顯示數據:github

import {User} from "./classes/User"; // ... export class AppComponent { users: User[] = [ new User('dreamapple', 22, '2312674832@qq.com'), new User('dreamapplehappy', 18, '2313334832@qq.com') ] }

別忘了在模板中添加一些展現數據使用的html代碼:typescript

<h1>依賴注入</h1> <ul> <li *ngFor="let user of users"> 用戶的姓名: {{user.name}}; 用戶的年齡: {{user.age}}; 用戶的郵箱: {{user.email}} </li> </ul>

而後咱們就會看到,在頁面中顯示出來了咱們想要的那些數據:
apache

Angular2的依賴注入segmentfault

通常狀況下在Web應用中,咱們要展現的數據都是從後臺服務器動態獲取的,因此咱們來模擬一下這個過程;咱們在這裏就要使用服務的依賴注入了,咱們首先建立文件user-data.mock.ts,路徑是app/mock/user-data.mock.ts;api

import {User} from "../classes/User"; export var Users:User[] = [ new User('dreamapple1', 21, '2451731631@qq.com'), new User('dreamapple2', 22, '2451731632@qq.com'), new User('dreamapple3', 23, '2451731633@qq.com'), new User('dreamapple4', 24, '2451731634@qq.com'), new User('dreamapple5', 25, '2451731635@qq.com'), new User('dreamapple6', 26, '2451731636@qq.com') ]

咱們使用了User類來建立咱們的數據,而後把建立的數據導出.

接下來咱們要建立一個獲取用戶數據的服務,咱們建立一個新的文件user.service.ts,路徑app/services/user.service.ts:

import {Injectable} from '@angular/core'; import {Users} from "../mock/user-data.mock"; @Injectable() export class UserService { getUsers() { return Users; } }

你們關於上面的代碼部分會有一些疑問,咱們來給你們解釋一下:首先咱們使用了剛纔咱們創造的模擬數據Users;而後咱們從@angular/core中導出了Injectable,就像咱們從中導出Component同樣;@Injectable()標誌着一個類能夠被一個注入器實例化;一般來說,在試圖實例化一個沒有被標識爲@Injectable()的類時候,注入器將會報告錯誤.上面的解釋如今不明白沒關係,咱們先學會如何使用;就像你不懂計算機原理同樣能夠把計算機玩得很溜同樣.

咱們接下來要在AppComponent組件中使用UserService了,須要注意的地方是:咱們要在@Component的元數據中使用providers聲明咱們所須要的依賴,還要引入User類來幫助咱們聲明數據的類型.

import {UserService} from "./services/user.service"; import {User} from "./classes/User"; //... @Component({ selector: 'my-app', //template: '<h1>My First Angular2 Travel</h1>', templateUrl: 'app/templates/app.html', providers: [ UserService ] }) export class AppComponent { users: User[]; constructor(private userService: UserService) { this.users = userService.getUsers(); } }

對上面代碼的一些解釋:咱們使用providers: [UserService]來聲明咱們這個組件的依賴,若是沒有這個選項,咱們的程序會報錯;而後咱們給這個類添加一個屬性users,同時聲明這個屬性的類型是一個含有User類實例的數組;最後咱們在構造函數中又聲明瞭一個私有的屬性userService,它是UserService服務類的一個實例,咱們能夠用這個實例來獲取users數據.

運行一下,而後咱們就會看到下面的頁面,表示一切成功.

若是這個時候你試圖把user.service.ts@Injectable註釋掉的話,整個程序是沒有報錯的,可是咱們建議爲每一個服務類都添加@Injectable(),包括那些沒有依賴因此技術上不須要它的.由於:(1)面向將來,沒有必要記得在後來添加了一個依賴的時候添加@Injectable().(2)一致性,全部的服務都遵循一樣的規則,而且咱們不須要考慮爲何少一個裝飾器.

這是由於,咱們的UserService服務如今尚未什麼依賴,若是咱們給UserService添加一個依賴的話,若是這時候把@Injectable()註釋掉的話,程序就會報錯;咱們來試試看吧.

不少Web程序都會須要一個日誌服務,因此咱們來新建一個服務Logger,路徑以下:app/services/logger.service.ts:

import {Injectable} from '@angular/core'; @Injectable() export class Logger{ logs: string[] = []; log(msg) { this.logs.push(msg); console.warn('From logger class: ' + msg); } }

而後咱們在UserService服務中使用這個服務:

import {Injectable} from '@angular/core'; import {Users} from "../mock/user-data.mock"; import {Logger} from "./logger.service"; @Injectable() export class UserService { constructor(private logger: Logger) { } getUsers() { this.logger.log('get users'); return Users; } }

能夠看到,咱們把Logger當作UserService服務的一個依賴,由於咱們在UserService類的構造函數中聲明瞭一個logger屬性,它是Logger類的一個實例;還有別忘了在AppComponent中添加這個Logger依賴:

@Component({ selector: 'my-app', //template: '<h1>My First Angular2 Travel</h1>', templateUrl: 'app/templates/app.html', providers: [ Logger, // 添加Logger依賴 UserService ] })

而後咱們能夠在頁面中看到:

若是這個時候,咱們註釋掉UserService@Injectable()的話,程序就會報錯:

因此,就像上面所說的;咱們仍是給每個服務類添加@Injectable(),以避免出現沒必要要的麻煩.

接下來咱們來討論一下在Angular2中服務的提供商們,若是你對所謂的提供商不理解的話,不要緊;能夠這樣理解,每當咱們使用一個服務的時候,Angular2都會經過提供商來建立或者獲取咱們想要的服務的實例.

咱們上面所說的那種提供服務的方式實際上是最簡單的一種方式,接下來咱們討論註冊不一樣的服務提供商的方法;首先第一種就是咱們上面所說的那種了,其實它是一種簡寫的方式;詳細的方式應該是這樣的:

[{ provide: Logger, useClass: Logger }]

其中provide做爲鍵值key使用,用於定位依賴,用於註冊這個提供商;這個其實就是在後面的程序中使用的服務的名字;useClass表示咱們使用哪個服務類去建立實例,固然咱們可使用不一樣的服務類,只要這些服務的類知足咱們相應的需求就行.

咱們能夠試着替換Logger類爲BetterLogger類,咱們首先建立BetterLogger類:

import {Injectable} from '@angular/core'; @Injectable() export class BetterLogger{ logs: string[] = []; log(msg) { this.logs.push(msg); console.warn('From better logger class: ' + msg); } }

而後在AppComponent中使用這個BetterLogger類:

@Component({ selector: 'my-app', //template: '<h1>My First Angular2 Travel</h1>', templateUrl: 'app/templates/app.html', providers: [ //Logger, [{provide: Logger, useClass: BetterLogger}], UserService ] })

咱們能夠看到,控制檯的輸出是:

從中能夠看到,咱們使用了BetterLogger類替換了Logger類.若是咱們的提供商須要一些依賴,咱們應該怎麼辦呢?不用怕,咱們可使用下面這種形式:

[ LoggerHelper,{ provide: Logger, useClass: Logger }]

接下來咱們來建立一個LoggerHelper類,它的路徑是app/services/logger-helper.service.ts:

import {Injectable} from '@angular/core'; @Injectable() export class LoggerHelper { constructor() { console.warn('Just a logger helper!'); } }

咱們在AppComponent中註冊提供商:

@Component({ selector: 'my-app', //template: '<h1>My First Angular2 Travel</h1>', templateUrl: 'app/templates/app.html', providers: [ //Logger, //[{provide: Logger, useClass: BetterLogger}], [LoggerHelper, {provide: Logger, useClass: BetterLogger}], // 帶有依賴的註冊商 UserService ] })

而後咱們在BetterLogger服務中使用這個依賴:

import {Injectable} from '@angular/core'; import {LoggerHelper} from "./logger-helper.service"; @Injectable() export class BetterLogger{ logs: string[] = []; constructor(private loggerHelper: LoggerHelper) { } log(msg) { this.logs.push(msg); console.warn('From better logger class: ' + msg); } }

而後能夠看到咱們的控制檯的輸出結果是:

說明咱們正確的使用了依賴;還有咱們可使用別名來使用相同的提供商,這種方式能夠解決一些問題;尤爲是當咱們想讓某個老的組件使用一個新的服務,就比如咱們想讓AppComponent使用BetterLogger類來打印日誌,而不是使用Logger類,假如咱們不可以改變AppComponent類,而且咱們還想讓其餘的組件也是用新的BetterLogger類的話,那麼咱們就能夠像下面這樣註冊這個提供商

[{ provide: BetterLogger, useClass: BetterLogger}], [{ provide: Logger, useExisting: BetterLogger}]

看到了嗎,咱們使用useExisting而不是useClass;由於使用useClass或致使咱們的應用中出現兩個BetterLogger類的實例.咱們能夠試驗一下,在AppComponent中:

@Component({ selector: 'my-app', //template: '<h1>My First Angular2 Travel</h1>', templateUrl: 'app/templates/app.html', providers: [ //Logger, //[{provide: Logger, useClass: BetterLogger}], [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}], [LoggerHelper, {provide: Logger, useExisting: BetterLogger}], UserService ] })

而後咱們在BetterLogger類的構造函數中添加一個打印語句:

console.warn('BetterLogger Constructor');

咱們還要在UserService類的構造函數中聲明一個屬性betterLogger,它是BetterLogger類的一個實例:

constructor(private logger: Logger, private betterLogger: BetterLogger) { }

最後咱們能夠看到控制檯的打印結果是:

Just a logger helper!
BetterLogger Constructor
From better logger class: get users 

可是一旦咱們使用了useClass而不是useExisting,那麼控制檯的打印結果就變成了:

Just a logger helper!
BetterLogger Constructor
BetterLogger Constructor
From better logger class: get users

說明咱們建立了兩個BetterLogger的實例.因此當咱們的多個服務想使用同一個提供商的時候,咱們應該使用useExisting,而不是useClass.

[2016-8-20:續寫]

值提供商:咱們可使用更簡便的方法來註冊一個提供商,那就是使用,所謂的值能夠是任何一種有效的TypeScript的基本的數據類型.咱們來首先使用一個對象吧.首先咱們新建立一個文件logger.value.ts,路徑是app/values/logger.value.ts;咱們寫一個基本的loggerValue對象以下:

let loggerValue = { logs: ['Hello', 'World'], log: (msg) => { console.warn('From values: ' + msg); }, hello: () => { console.log('Just say hello!'); } }; export {loggerValue};

那咱們如何註冊這個提供商呢?咱們使用useValue選項來註冊咱們這種提供商;以下所示:

// ... providers: [ //Logger, //[{provide: Logger, useClass: BetterLogger}], [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}], //[LoggerHelper, {provide: Logger, useClass: BetterLogger}], {provide: Logger, useValue: loggerValue}, //{provide: Logger, useValue: loggerValue1}, // 咱們使用了useValue選項 UserService ] // ... 

還要記住把loggerValue導入進來;而後咱們稍微修改一下user.service.ts的代碼:

// ... getUsers() { this.logger.log('get users'); //noinspection TypeScriptUnresolvedFunction this.logger.hello(); return Users; } // ...

而後咱們會看到控制檯的輸出是:

// ... From values: get users Just say hello! // ...

代表咱們這種方式註冊提供商成功了. 固然咱們也可使用一個字符串了,這些讀者能夠自行嘗試;或者觀看這個示例.

工廠提供商:有時咱們須要動態建立這個依賴值,由於它所須要的信息咱們直到最後一刻才能肯定;咱們如何註冊一個工廠提供商呢?不着急,咱們一步一步來:咱們首先來建立一個驗證權限的文件,authorize.ts,路徑是:app/services/authorize.ts,咱們暫且在裏面放置一些簡單的邏輯,來斷定當前用戶有沒有獲取Users的權限:

import {Injectable} from '@angular/core'; @Injectable() export class Authorize { isAuthorized: boolean; constructor(){ this.isAuthorized = Math.random() > 0.5 ? true: false; } getIsAuthorized() { return this.isAuthorized; } }

好吧,我認可這樣寫有點隨意,暫時先這樣吧;咱們的目的是爲了告訴你們如何使用工廠提供商,暫時簡化權限驗證這一塊;從上面的代碼咱們能夠大概瞭解到,這個服務就是爲了獲取當前用戶的權限狀況;而後咱們來配置咱們的UserService2Provider,爲了方便咱們暫時直接在app.component.ts中書寫咱們的配置:

// ... let UserService2Provider = (logger: Logger, authorize: Authorize) => { return new UserService2(logger, authorize.getIsAuthorized()); }; // ...

能夠看到,咱們的UserService2Provider其實就是一個返回了類的實例的一個函數;咱們給這個函數傳遞了兩個參數,分別是LoggerAuthorize類的實例,而後咱們根據這兩個實例,建立出了咱們新的服務實例;奧,忘了告訴你們,咱們還要建立一個新的UserService2類,路徑:app/services/user-service2.ts:

import {Injectable} from '@angular/core'; import {Users} from "../mock/user-data.mock"; import {Logger} from "./logger.service"; @Injectable() export class UserService2 { isAuthorized: boolean; constructor(private logger: Logger, isAuthorized: boolean) { this.isAuthorized = isAuthorized; } getUsers() { if(this.isAuthorized){ this.logger.log('get users'); return Users; } else { this.logger.log('not isAuthorized!'); return []; } } }

能夠看到這個服務類和UserService差很少,就是多了一個條件驗證,若是當前用戶有獲取Users的權限,咱們就會返回這些Users;若是沒有,咱們就返回一個空數組.接下來就是很重要的一步了,咱們須要在app.component.tsproviders中使用UserService2Provider:

// ... providers: [ //Logger, //[{provide: Logger, useClass: BetterLogger}], [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}], //[LoggerHelper, {provide: Logger, useClass: BetterLogger}], {provide: Logger, useValue: loggerValue}, //{provide: Logger, useValue: loggerValue1}, UserService, Authorize, // 不可缺乏 { provide: UserService2, useFactory: UserService2Provider, deps: [Logger, Authorize] } ] // ... 

還要記住,要添加Authorize依賴;咱們在app.component.ts中使用新的服務:

// ... export class AppComponent { users: User[]; constructor(private userService: UserService, private userService2: UserService2) { this.users = userService.getUsers(); console.log(this.userService2.getUsers()); } }

刷新瀏覽器,你會看到有時它會輸出:

From values: not isAuthorized! []

有時它會輸出:

From values: get users [User, User, User, User, User, User]

那麼說明,咱們這種方式註冊工廠提供商的方式也成功了.

也許你們會有一些疑問,咱們在類的構造函數中使用private userService2: UserService2怎麼就獲取了這個服務的一個示例,Angular2是怎麼知道咱們要的是UserService2類,它又是如何獲取這個類的實例的呢?在Angular2中咱們經過注射器來進行依賴注入,其實上面的形式只是一種簡寫;詳細一點的寫法是這樣的:

import {Component, Injector} from '@angular/core';

咱們先從Ng2中獲取Injector注射器,而後使用這個注射器來進行咱們所需服務的實例化:

// ... export class AppComponent { users: User[]; private userService2: UserService2; //constructor(private userService: UserService, private userService2: UserService2) { // this.users = userService.getUsers(); // // console.log(this.userService2.getUsers()); //} constructor(private userService: UserService, private injector: Injector) { this.users = userService.getUsers(); this.userService2 = this.injector.get(UserService2); console.log(this.userService2.getUsers()); } }

因此能夠看出來,這些繁瑣的活咱們所有都讓injector去作了,只須要咱們提供一些簡單的說明,聰明的Ng2就知道如何進行依賴注入.

非類依賴

咱們上面的講解所有都是把一個類做爲一個依賴來進行服務的依賴注入,可是假如咱們想要的不是一個類,而是一些值,或者對象;咱們應該怎麼辦?咱們先來寫出這麼一個文件app-config.ts,路徑是:app/config/app-config.ts:

export interface AppConfig { title: string, apiEndPoint: string } export const AppConf: AppConfig = { title: 'Dreamapple', apiEndPoint: 'https://hacking-with-angular.github.io/' };

按照上面的使用的方法,咱們應該能夠這樣作:

// ... {provide: AppConfig, useValue: AppConf} // ... constructor(private userService: UserService, private userService2: UserService2, private appConf: AppConfig) { this.users = userService.getUsers(); console.log(this.userService2.getUsers()); console.log(this.appConf); } // ... 

可是咱們這樣作卻沒有達到咱們想要的效果;控制檯報錯:

Error: ReferenceError: AppConfig is not defined(…)

由於接口interface不可以被當作一個類class來處理,因此咱們須要換一種新的方式,使用OpaqueToken(不透明的令牌):

// 首先導入 OpaqueToken和Inject import {Component, Injector, OpaqueToken, Inject} from '@angular/core'; // 引入AppConf,而且使用OpaqueToken import {AppConf} from "./config/app-config"; let APP_CONFIG = new OpaqueToken('./config/app-config'); // 在providers中進行配置 {provide: APP_CONFIG, useValue: AppConf} // 在類中使用 constructor(private userService: UserService, private userService2: UserService2, @Inject(APP_CONFIG) appConf: AppConfig) { this.users = userService.getUsers(); console.log(this.userService2.getUsers()); console.log(appConf); }

對上面的代碼的一些解釋首先咱們使用OpaqueTokean對象註冊依賴的提供商,而後咱們在@Inject的幫助下,咱們把這個配置對象注入到須要它的構造函數中,最後咱們就可使用最初的那個對象了.雖然AppConfig接口在依賴注入過程當中沒有任何做用,但它爲該類中的配置對象提供了強類型信息.

最後咱們來說解一下可選依賴(Optional),有些服務的依賴也許不是必須的;咱們就可使用@Optional()來對這些參數作標記;記住,當使用@Optional()時,若是咱們想標記logger這個服務是可選的;那麼若是咱們不在組件或父級組件中註冊一個logger的話,注入器會設置該logger的值爲空null;咱們的代碼必需要爲一個空值作準備.來看一下例子吧,咱們在user.service.ts中作一些改動:

 
// 導入 Optonal import {Injectable, Optional} from '@angular/core'; import {Users} from "../mock/user-data.mock"; import {Logger} from "./logger.service"; import {BetterLogger} from "./better-logger.service"; @Injectable() export class UserService { constructor(private logger: Logger, // 使用@Optional標記 @Optional()private betterLogger: BetterLogger) { } getUsers() { this.logger.log('get users'); //noinspection TypeScriptUnresolvedFunction this.logger.hello(); // 存在betterLogger時的處理 if(this.betterLogger) { this.betterLogger.log('optional'); } //console.log(this.logger); return Users; } }

至此,整篇文章已經結束了;若是你堅持讀到了這裏,那說明你也是一個頗有耐心的人;若是你有什麼問題能夠在這裏提出.

相關文章
相關標籤/搜索