[從 0 開始的 Angular 生活]No.33 建立 Angular 服務元件與實作相依注入

前言

到目前為止大部分的程式碼都放在 ArticleModule ,而這個 Module 內包含三個元件,其中 ArticleList 是父元件 ; ArticleHeader 與 ArticleBody 為子元件。大部分的程式邏輯與資料全部都放在 ArticleList 內,那麼我們要如何利用服務元件來協助處理這部分呢?

服務元件

服務元件是一個類別,而類別內只有兩種東西

  • 屬性
  • 方法

既然類別內只有這兩種東西,此時就可以想著該把什麼東西給獨立抽離使其變成服務元件。

以 ArticleList 元件為例
在設計元件的時候,通常會把相關的資料或邏輯放在同一的元件做管理。

而這個元件的 OnInit() 內放有文章的初始化資料內容,因此:

  • 可以把這些資料與資料處理邏輯抽離,建立一個 Data Service 的服務元件

建立一個服務元件 - Data Service

建立的方式同樣透過 Angular CLI 指令,以下則一即可:

  • ng generate service 名稱
  • ng g s data 名稱

建立成功

此時會發現專案內多了兩支檔案,單元測試檔及服務元件主要的程式碼。

data.service.ts

1
2
3
4
5
6
7
8
9
import { Injectable } from '@angular/core';

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

constructor() { }
}

這樣的程式碼結構是不是跟之前建立元件時看到的很相似呢?

註冊 Data Service

建立好 Data Service 後,接著還需要將其註冊進模組 (Module) 內才可以使用。

目前有兩個模組,分別是 AppModule 以及 ArticleModule

  • 要選擇的是 ArticleModule ,因為我們是要幫這個模組建立服務元件

如果要將服務元件註冊進模組內,必須:

  • 加入一個 providers 屬性,值為陣列型別
    • 這樣就可以放入多個服務的提供者,如
      • providers: [DataService]

article.module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ArticleListComponent } from './article-list/article-list.component';
import { ArticleHeaderComponent } from './article-header/article-header.component';
import { ArticleBodyComponent } from './article-body/article-body.component';
import { FormsModule } from '@angular/forms';
import { DataService } from '../data.service';

@NgModule({
declarations: [ArticleListComponent, ArticleHeaderComponent, ArticleBodyComponent],
imports: [
CommonModule,
FormsModule
],
providers: [DataService],
exports: [ArticleListComponent]
})
export class ArticleModule { }

這樣子服務元件就註冊完成了!

而任何一種元件內都可以透過相依注入 (DI) ,把服務元件取出來使用。

測試 Data Service

目前 Data Service 的確沒有任何程式碼在裡面,但我們可以做個小測試驗證一下。

準備 Data Service 內的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Injectable } from '@angular/core';

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

constructor() { }

run() {
console.log('DataService!');
}
}

將服務注入到元件內

接著使用相依注入,注入到 ArticleList 內。

如何進行相依注入:

  • 宣告一個 dService 屬性,並且利用 TypeScript 宣告型別為 DataService
    • 不一定會叫做 DataServie ,是根據剛才建立的服務名稱不同也會改變
  • 在元件的 constructor () 內宣告一個參數 datasvc ,並且利用 TypeScript 宣告型別為 DataService
    • 當我們透過建構式成功注入時,將 dService 屬性值給定 datasvc 參數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component, OnInit } from '@angular/core';
import { DataService } from 'src/app/data.service';

@Component({
selector: 'app-article-list',
templateUrl: './article-list.component.html',
styleUrls: ['./article-list.component.scss']
})
export class ArticleListComponent implements OnInit {
atticleData: Array<any>;
counter = 0;
dService: DataService;
constructor(datasvc: DataService) {
this.dService = datasvc;
}
//...以下省略
}

第一次做相依注入的同時 Angular 會自動的 new 出 DataService 的 class ,所以參數 datasvc 得到的其實是一個物件的實體

而不管有幾個 Component 要注入相同的元件,在預設的情況下 Angular 只會 new 一次,也就是最終只會得到一個唯一的物件實體。

如此一來不管是在任何的 Component 內,只要是相同的服務元件,就可以確保得到的物件是唯一的,因此可以更輕易的在不同元件間共享一些必要的資料。

將服務注入到元件內 - 利用 TypeScript

透過 TypeScript 有效的簡化使用相依注入時的語法

  • 在建構式傳入的參數前面加上 publicprivate
    • 加上之後 TypeScript 預設會在 ArticleList 這個元件的類別自動宣告一個 DataServie 型別的屬性
  • 也就是說屬性不再需要自己額外宣告了,只需要宣告建構式內的參數即可。
  • 也不再需要將注入成功的值賦予給屬性

修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Component, OnInit } from '@angular/core';
import { DataService } from 'src/app/data.service';

@Component({
selector: 'app-article-list',
templateUrl: './article-list.component.html',
styleUrls: ['./article-list.component.scss']
})
export class ArticleListComponent implements OnInit {
atticleData: Array<any>;
counter = 0;
constructor(private datasvc: DataService) {
}
//...以下省略
}

修改後一行就搞定了~而且完全等價於剛剛三行的寫法。

所以未來再進行其他的相依注入時,只是要適當地在建構式參數前面加上 publicprivate ,後面再透過 TypeScript 的型別標註,就可以快速完成 DI 。

如何在元件內使用注入的服務元件

以 ngOnInit() 為例:

  • 直接調用剛才傳入建構式的參數名 (此時是 DataService 物件),可以印出觀察
  • 因為是物件,可以使用 . 運算子執行裡面的方法
1
2
3
4
ngOnInit() {
console.log(this.datasvc);
this.datasvc.run();
}

最後,看看結果是否如我們預期吧!

小結

本篇介紹了如何建立服務元件以及注入到其他元件內,下一篇我們要使用這個技巧替 ArticleList 進行重構。

0%