- Angular 8 教程
- Angular 8 - 首頁
- Angular 8 - 簡介
- Angular 8 - 安裝
- 建立第一個應用程式
- Angular 8 - 架構
- Angular 元件和模板
- Angular 8 - 資料繫結
- Angular 8 - 指令
- Angular 8 - 管道
- Angular 8 - 響應式程式設計
- 服務和依賴注入
- Angular 8 - Http 客戶端程式設計
- Angular 8 - Angular Material
- 路由和導航
- Angular 8 - 動畫
- Angular 8 - 表單
- Angular 8 - 表單驗證
- 身份驗證和授權
- Angular 8 - Web Workers
- Service Workers 和 PWA
- Angular 8 - 伺服器端渲染
- Angular 8 - 國際化 (i18n)
- Angular 8 - 可訪問性
- Angular 8 - CLI 命令
- Angular 8 - 測試
- Angular 8 - Ivy 編譯器
- Angular 8 - 使用 Bazel 構建
- Angular 8 - 向後相容性
- Angular 8 - 工作示例
- Angular 9 - 有什麼新功能?
- Angular 8 有用資源
- Angular 8 - 快速指南
- Angular 8 - 有用資源
- Angular 8 - 討論
Angular 8 - 服務和依賴注入
如前所述,服務在 Angular 應用程式中提供特定功能。在給定的 Angular 應用程式中,可以使用一個或多個服務。同樣,Angular 元件可能依賴於一個或多個服務。
此外,Angular 服務可能依賴於其他服務才能正常工作。依賴項解析是開發任何應用程式中複雜且耗時的活動之一。為了降低複雜性,Angular 提供了依賴注入模式作為其核心概念之一。
讓我們在本節中學習如何在 Angular 應用程式中使用依賴注入。
建立 Angular 服務
Angular 服務是一個普通的 TypeScript 類,它具有一個或多個方法(功能)以及@Injectable裝飾器。它使普通的 TypeScript 類能夠用作 Angular 應用程式中的服務。
import { Injectable } from '@angular/core'; @Injectable()
export class DebugService {
constructor() { }
}
這裡,@Injectable裝飾器將普通的 TypeScript 類轉換為 Angular 服務。
註冊 Angular 服務
要使用依賴注入,每個服務都需要註冊到系統中。Angular 提供了多種註冊服務的方法。它們如下所示:
- ModuleInjector @ 根級別
- ModuleInjector @ 平臺級別
- 使用 providers 元資料的 ElementInjector
- 使用 viewProviders 元資料的 ElementInjector
- NullInjector
ModuleInjector @ 根
ModuleInjector強制服務僅在特定模組內使用。@Injectable中提供的providedIn元資料必須用於指定可以使用該服務的模組。
該值應引用已註冊的 Angular 模組之一(用@NgModule裝飾)。root是一個特殊選項,它引用應用程式的根模組。示例程式碼如下:
import { Injectable } from '@angular/core'; @Injectable({
providedIn: 'root',
})
export class DebugService {
constructor() { }
}
ModuleInjector @ 平臺
Platform Injector比ModuleInject高一級,並且僅在高階和罕見的情況下使用。每個 Angular 應用程式都透過執行PreformBrowserDynamic().bootstrap方法(參見main.js)開始,該方法負責引導 Angular 應用程式的根模組。
PreformBrowserDynamic()方法建立由PlatformModule配置的注入器。我們可以使用PlatformModule提供的platformBrowser()方法配置平臺級服務。
NullInjector
NullInjector比平臺級ModuleInjector高一級,並且位於層次結構的頂層。我們無法在NullInjector中註冊任何服務。當在層次結構中的任何地方都找不到所需的伺服器時,它會解析並簡單地丟擲錯誤。
使用 providers 的 ElementInjector
ElementInjector強制服務僅在某些特定元件內使用。@Component裝飾器中提供的 providers 和ViewProviders元資料用於指定對特定元件可見的服務列表。使用 providers 的示例程式碼如下:
ExpenseEntryListComponent
// import statement
import { DebugService } from '../debug.service';
// component decorator
@Component({
selector: 'app-expense-entry-list',
templateUrl: './expense-entry-list.component.html',
styleUrls: ['./expense-entry-list.component.css'],
providers: [DebugService] })
這裡,DebugService僅在ExpenseEntryListComponent及其檢視內部可用。要在其他元件中使用 DebugService,只需在必要的元件中使用providers裝飾器即可。
使用 viewProviders 的 ElementInjector
viewProviders類似於provider,只是它不允許在使用ng-content指令建立的元件內容內部使用該服務。
ExpenseEntryListComponent
// import statement
import { DebugService } from '../debug.service';
// component decorator
@Component({
selector: 'app-expense-entry-list',
templateUrl: './expense-entry-list.component.html',
styleUrls: ['./expense-entry-list.component.css'], viewProviders: [DebugService]
})
父元件可以透過其檢視或內容使用子元件。下面提到了一個帶有子元件和內容檢視的父元件的示例:
父元件檢視/模板
<div> child template in view <child></child> </div> <ng-content></ng-content>
子元件檢視/模板
<div> child template in view </div>
父元件在模板(另一個元件)中的用法
<parent> <!-- child template in content --> <child></child> </parent>
這裡,
- 子元件在兩個地方使用。一個在父元件的檢視內。另一個在父元件的內容內。
- 服務將在子元件中可用,該子元件放置在父元件的檢視內。
- 服務在子元件中不可用,該子元件放置在父元件的內容內。
解析 Angular 服務
讓我們看看元件如何使用下面的流程圖解析服務。
這裡,
- 首先,元件嘗試查詢使用viewProviders元資料註冊的服務。
- 如果未找到,元件嘗試查詢使用providers元資料註冊的服務。
- 如果未找到,元件嘗試查詢使用ModuleInjector註冊的服務
- 如果未找到,元件嘗試查詢使用PlatformInjector註冊的服務
- 如果未找到,元件嘗試查詢使用NullInjector註冊的服務,它始終會丟擲錯誤。
注入器的層次結構以及解析服務的流程如下所示:
解析修飾符
正如我們在上一節中瞭解到的,服務的解析從元件開始,並在找到服務或到達NUllInjector時停止。這是預設解析,可以使用解析修飾符更改它。它們如下所示:
Self()
Self()在其當前ElementInjector本身中啟動和停止對服務的搜尋。
import { Self } from '@angular/core';
constructor(@Self() public debugService: DebugService) {}
SkipSelf()
SkipSelf()與 Self() 正好相反。它跳過當前的 ElementInjector,並從其父ElementInjector開始搜尋服務。
import { SkipSelf } from '@angular/core';
constructor(@SkipSelf() public debugService: DebugService) {}
Host()
Host()停止在其宿主ElementInjector中搜索服務。即使服務在更高層可用,它也會在宿主處停止。
import { Host } from '@angular/core';
constructor(@Host() public debugService: DebugService) {}
Optional()
Optional()在搜尋服務失敗時不會丟擲錯誤。
import { Optional } from '@angular/core';
constructor(@Optional() private debugService?: DebugService) {
if (this.debugService) {
this.debugService.info("Debugger initialized");
}
}
依賴注入提供程式
依賴注入提供程式有兩個作用。首先,它有助於為要註冊的服務設定令牌。該令牌將用於引用和呼叫服務。其次,它有助於根據給定的配置建立服務。
如前所述,最簡單的提供程式如下所示:
providers: [ DebugService ]
這裡,DebugService既是令牌,也是用於建立服務物件的類。提供程式的實際形式如下:
providers: [ { provides: DebugService, useClass: DebugService }]
這裡,provides是令牌,useClass是用於建立服務物件的類引用。
Angular 提供了一些其他的提供程式,它們如下所示:
別名類提供程式
提供程式的目的是重用現有的服務。
providers: [ DebugService,
{ provides: AnotherDebugService, userClass: DebugService }]
這裡,只會建立一個DebugService服務的例項。
值提供程式
值提供程式的目的是提供值本身,而不是要求 DI 建立服務物件的例項。它也可以使用現有的物件。唯一的限制是物件必須與引用的服務形狀相同。
export class MyCustomService {
name = "My Custom Service"
}
[{ provide: MyService, useValue: { name: 'instance of MyCustomService' }]
這裡,DI 提供程式只是返回在useValue選項中設定的例項,而不是建立新的服務物件。
非類依賴項提供程式
它允許在 Angular DI 中使用字串、函式或物件。
讓我們看一個簡單的例子。
// Create the injectable token
import { InjectionToken } from '@angular/core';
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
// Create value
export const MY_CONFIG: AppConfig = {
title: 'Dependency Injection'
};
// congfigure providers
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
// inject the service
constructor(@Inject(APP_CONFIG) config: AppConfig) {
工廠提供程式
工廠提供程式可以建立複雜的伺服器。它將物件的建立委託給外部函式。工廠提供程式還可以選擇為工廠物件設定依賴項。
{ provide: MyService, useFactory: myServiceFactory, deps: [DebugService] };
這裡,myServiceFactory返回MyService的例項。
Angular 服務用法
現在,我們知道了如何建立和註冊 Angular 服務。讓我們看看如何在元件內使用 Angular 服務。使用 Angular 服務就像將建構函式的引數型別設定為服務提供程式的令牌一樣簡單。
export class ExpenseEntryListComponent implements OnInit {
title = 'Expense List';
constructor(private debugService : DebugService) {}
ngOnInit() {
this.debugService.info("Angular Application starts");
}
}
這裡,
ExpenseEntryListComponent建構函式將型別為 DebugService 的引數設定為引數。
Angular 依賴注入器(DI)將嘗試查詢應用程式中註冊的任何型別為 DebugService 的服務。如果找到,它將把 DebugService 的例項設定為 ExpenseEntryListComponent 元件。如果未找到,它將丟擲錯誤。
新增除錯服務
讓我們新增一個簡單的Debug服務,它將幫助我們在應用程式開發期間列印除錯資訊。
開啟命令提示符並轉到專案根資料夾。
cd /go/to/expense-manager
啟動應用程式。
ng serve
執行以下命令生成 Angular 服務DebugService。
ng g service debug
這將建立兩個 TypeScript 檔案(除錯服務及其測試),如下所示:
CREATE src/app/debug.service.spec.ts (328 bytes) CREATE src/app/debug.service.ts (134 bytes)
讓我們分析DebugService服務的內容。
import { Injectable } from '@angular/core'; @Injectable({
providedIn: 'root'
})
export class DebugService {
constructor() { }
}
這裡,
@Injectable裝飾器附加到 DebugService 類,這使得 DebugService 能夠在應用程式的 Angular 元件中使用。
providerIn選項及其值root使得 DebugService 能夠在應用程式的所有元件中使用。
讓我們新增一個方法Info,它將訊息列印到瀏覽器控制檯。
info(message : String) : void {
console.log(message);
}
讓我們在ExpenseEntryListComponent中初始化服務並使用它來列印訊息。
import { Component, OnInit } from '@angular/core'; import { ExpenseEntry } from '../expense-entry'; import { DebugService } from '../debug.service'; @Component({
selector: 'app-expense-entry-list',
templateUrl: './expense-entry-list.component.html', styleUrls: ['./expense-entry-list.component.css']
})
export class ExpenseEntryListComponent implements OnInit {
title: string;
expenseEntries: ExpenseEntry[];
constructor(private debugService: DebugService) { }
ngOnInit() {
this.debugService.info("Expense Entry List
component initialized");
this.title = "Expense Entry List";
this.expenseEntries = this.getExpenseEntries();
}
// other coding
}
這裡,
DebugService 使用建構函式引數進行初始化。設定型別為 DebugService 的引數(debugService)將觸發依賴注入以建立新的 DebugService 物件並將其設定為 ExpenseEntryListComponent 元件。
在 ngOnInit 方法中呼叫 DebugService 的 info 方法將在瀏覽器控制檯中列印訊息。
可以使用開發者工具檢視結果,它看起來類似於以下所示:
讓我們擴充套件應用程式以瞭解服務的範圍。
讓我們使用以下命令建立一個DebugComponent。
ng generate component debug CREATE src/app/debug/debug.component.html (20 bytes) CREATE src/app/debug/debug.component.spec.ts (621 bytes) CREATE src/app/debug/debug.component.ts (265 bytes) CREATE src/app/debug/debug.component.css (0 bytes) UPDATE src/app/app.module.ts (392 bytes)
讓我們刪除根模組中的 DebugService。
// src/app/debug.service.ts
import { Injectable } from '@angular/core'; @Injectable()
export class DebugService {
constructor() {
}
info(message : String) : void {
console.log(message);
}
}
在 ExpenseEntryListComponent 元件下注冊 DebugService。
// src/app/expense-entry-list/expense-entry-list.component.ts @Component({
selector: 'app-expense-entry-list',
templateUrl: './expense-entry-list.component.html',
styleUrls: ['./expense-entry-list.component.css']
providers: [DebugService]
})
這裡,我們使用了 providers 元資料(ElementInjector)來註冊服務。
開啟DebugComponent (src/app/debug/debug.component.ts) 並匯入DebugService,並在元件的建構函式中設定例項。
import { Component, OnInit } from '@angular/core'; import { DebugService } from '../debug.service';
@Component({
selector: 'app-debug',
templateUrl: './debug.component.html',
styleUrls: ['./debug.component.css']
})
export class DebugComponent implements OnInit {
constructor(private debugService: DebugService) { }
ngOnInit() {
this.debugService.info("Debug component gets service from Parent");
}
}
這裡,我們沒有註冊DebugService。因此,如果用作父元件,則 DebugService 將不可用。當在父元件內使用時,如果父元件可以訪問服務,則該服務可能來自父元件。
開啟ExpenseEntryListComponent模板 (src/app/expense-entry-list/expense-entry-list.component.html) 幷包含如下所示的內容部分
// existing content <app-debug></app-debug> <ng-content></ng-content>
這裡,我們包含了一個內容部分和 DebugComponent 部分。
讓我們在 AppComponent 模板中將除錯元件作為內容包含在ExpenseEntryListComponent元件中。開啟AppComponent模板並將app-expense-entry-list更改為如下所示:
// navigation code <app-expense-entry-list> <app-debug></app-debug> </app-expense-entry-list>
這裡,我們包含了DebugComponent作為內容。
讓我們檢查應用程式,它將在頁面末尾顯示DebugService模板,如下所示:
此外,我們可以在控制檯中從除錯元件看到兩條除錯資訊。這表明除錯元件從其父元件獲取服務。
讓我們更改服務在**ExpenseEntryListComponent**中注入的方式以及它如何影響服務的範圍。將providers注入器更改為viewProviders注入。**viewProviders**不會將服務注入到內容子元素中,因此它應該會失敗。
viewProviders: [DebugService]
檢查應用程式,您將看到其中一個除錯元件(用作內容子元素)丟擲如下所示的錯誤:
讓我們從模板中刪除除錯元件並恢復應用程式。
開啟**ExpenseEntryListComponent**模板(src/app/expense-entry-list/expense-entry-list.component.html)並刪除以下內容
<app-debug></app-debug> <ng-content></ng-content>
開啟**AppComponent**模板並將**app-expense-entry-list**更改為如下所示:
// navigation code <app-expense-entry-list> </app-expense-entry-list>
在**ExpenseEntryListComponent**中將**viewProviders**設定更改為**providers**。
providers: [DebugService]
重新執行應用程式並檢查結果。