[從 0 開始的 Angular 生活]No.34 透過服務元件重構現有的元件程式碼

前言

呈上篇,在掌握如何使用服務元件並且透過 DI 將其注入元件中使用後,緊接著我們就可以利用此技巧將原本元件內的邏輯抽出進行重構了。

判斷服務元件的範圍

在這個例子中 Data Service 只會用在 ArticleModule ,所以最好是把 data.service 的兩隻檔案都搬進去模組資料夾內,比較方便管理。

但是現在的情況下並不能直接移動,因為這個服務很可能在不同地方被引用了,一旦移動位置就必須跟著改其他地方。

做法有二:

  • 在建立服務元件時就必須確定這個服務元件要被放在哪個模組下,這樣就 OK 了
  • 或者使用 Move TS 這個 VS Code 上的插件,協助移動 TS 檔案

現在這個情況就使用 Move TS 來處理吧!

如果已經裝好了這個插件,那麼在移動 TS 檔案的時候,這個插件就會自動地幫我們把所有參考到這個檔案的 TypeScript 檔一併修改路徑,讓我們測試看看。

移動前

  • 對著要移動的檔案點右鍵,使用 Move TS 來搬運檔案

輸入指定資料夾

搬運成功

移動後

搬運並重構程式碼

接著開始搬運 ArticleList 的部分程式碼包含資料與邏輯的部分,都搬進 Data Service 。

Data Service

  • 把 doDelete() 、 doChange() 搬出去
    • 但搬運到服務元件內之後,此時方法的命名就沒這麼好了,因此也需要調整
  • 搬運文章的資料並建立屬性 articleData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root'
})
export class DataService {
atticleData: Array<any>;
constructor() {
/* tslint:disable */
this.atticleData = [
{
"id": 1,
"href": "http://blog.miniasp.com/post/2016/04/30/Visual-Studio-Code-from-Command-Prompt-notes.aspx",
"title": "從命令提示字元中開啟 Visual Studio Code 如何避免顯示惱人的偵錯訊息",
"date": "2016/04/30 18:05",
"author": "GHJKL",
"category": "Visual Studio",
"category-link": "http://blog.miniasp.com/category/Visual-Studio.aspx",
"summary": "<p>由於我的 Visual Studio Code 大部分時候都是在命令提示字元下啟動,所以只要用 <strong><font color='#ff0000' face='Consolas'>code .</font></strong>就可以快速啟動 Visual Studio Code 並自動開啟目前所在資料夾。不過不知道從哪個版本開始,我在啟動 Visual Studio Code 之後,卻開始在原本所在的命令提示字元視窗中出現一堆惱人的偵錯訊息,本篇文章試圖解析這個現象,並提出解決辦法。</p><p>... <a class='more' href='http://blog.miniasp.com/post/2016/04/30/Visual-Studio-Code-from-Command-Prompt-notes.aspx#continue'>繼續閱讀</a>...</p>"
},
//...以下省略...
];
}

doDelete(item){
this.atticleData = this.atticleData.filter(v => v.id !== item.id);
}
doModify(post: any){
this.atticleData = this.atticleData.map((item)=>{
if(post.id === item.id) {
return Object.assign({}, item, post);
}
return item;
});
}
}

這樣 Data Service 的部分算是處理好了,接下來回到 ArticleList 。

ArticleList

  • 由於我們先前在測試時已經時做了相依注入
    • 所以在建構式執行完之後會得到一個 Data Service 物件的實體
      • 而且存在 datasvc 這個屬性裡面,因此可以從這取得原本文章的資料維持畫面的呈現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

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

}

程式碼都搬運了,自然 Template 的部分也需要做調整:

  • 因為 doDelete() 以及 doModify() 已經被轉移到 Data Service ,所以必須做調整
    • 使用 datasvc 屬性即可取用服務元件下的方法
      • 特別注意如果前面冠有 private 是無法在 Template 內取用的,要改成 public
        • 因此將其修正為 public

修正後即可順利取用方法

1
2
3
4
5
6
<!-- Article START-->
<article class="post" id="post{{idx}}" *ngFor="let item of atticleData; let idx = index">
<app-article-header [item]="item" (delete)="datasvc.doDelete($event)" (changeTitle)="datasvc.doModify($event)"></app-article-header>
<app-article-body [item]="item" [counter]="counter"></app-article-body>
</article>
<!-- Article END-->

測試運行

迫不及待地馬上測試看看!

然後會發現完全沒有效果。

為什麼?明明 console.log 也沒有錯誤訊息?

問題在於,在 JavaScript 中有個非常重要的語言特性是:

  • 所有的屬性、變數物件參考到的永遠是物件的本身

什麼意思呢? 先來看看服務元件內 doModify() 的寫法

1
2
3
4
5
6
7
8
doModify(post: any){
this.atticleData = this.atticleData.map((item)=>{
if(post.id === item.id) {
return Object.assign({}, item, post);
}
return item;
});
}

意思是 this.atticleData 透過 map() 被賦值一個全新的物件

  • 所以每一次進行 doModify() 時所得到的 atticleData 值都是一個全新的物件

但是第一次進行相依注入時, ArticleList 元件內的 ngOnInit() 我們是這麼寫的:

1
2
3
ngOnInit() {
this.atticleData = this.datasvc.atticleData;
}

就是把 this.datasvc.atticleData 所指向的物件參考傳給了 atticleData 這個屬性

所以當服務元件 DataService 內重新又建立一個元件時,這裡的 ngOnInit() 可不會重新又執行一次,也就是說我們在 DataService 上做的任何修改 ArticleList 完全看不到。

如何修改

既然方法可以直接從服務元件內取用,那麼資料想必也是可以的。

所以我們修改 ArticleList 的 Template

1
2
3
4
5
6
<!-- Article START-->
<article class="post" id="post{{idx}}" *ngFor="let item of datasvc.atticleData; let idx = index">
<app-article-header [item]="item" (delete)="datasvc.doDelete($event)" (changeTitle)="datasvc.doModify($event)"></app-article-header>
<app-article-body [item]="item" [counter]="counter"></app-article-body>
</article>
<!-- Article END-->

因為改用 datasvc.atticleData 所以可以刪除 class 內不再用到的 atticleData 屬性

  • 因為資料的來源就是由服務元件提供,自然就不需要寫 this.atticleData = this.datasvc.atticleData;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

@Component({
selector: 'app-article-list',
templateUrl: './article-list.component.html',
styleUrls: ['./article-list.component.scss']
})
export class ArticleListComponent implements OnInit {
counter = 0;
constructor(public datasvc: DataService) {
}
ngOnInit() {
setTimeout(() => {
this.counter++;
}, 2000);
}
}

於是現在這個 ArticleList 元件的 class 基本上快被搬光了,只剩下一個服務元件 Data Service 的注入,所有的邏輯都在服務元件上,而 Template 是直接取用服務元件上的資料以及邏輯。

再次進行測試
編輯成功

刪除成功

大功告成!

小結

實作完成後發現服務元件蠻吃 JavaScript 觀念,如果基礎沒有打穩,當發生這個 Bug 時肯定找不到問題,更別說這個問題連開發者工具都沒顯示錯誤,若基礎不穩肯定不知道發生什麼事了。

0%