[從 0 開始的 Angular 生活]No.36 重構 DataService 服務元件

前言

用得好好的為啥要重構呢?原因是目前這個 DataService 並沒有寫得很漂亮,原因是通常不會直接在服務元件內直接做 .subscribe() 的動作,大部分的情況都是在其他元件內進行。畢竟什麼時候要訂閱是各個元件自己才知道,服務元件單純的提供服務就好。

重構 DataService

移除先前為了介紹寫在建構式內的程式碼,並且新增一個方法叫 getData()

  • httpClient.get() 方法會最後會產生一個觀察者物件,將其回傳
    • 而這麼做,意味著資料最後不會出現在服務元件內,因此先註解其他程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private httpClient: HttpClient) {
}
getData() {
return this.httpClient.get('http://localhost:4200/api/articles.json');
}
// 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;
// });
// }
}

調整 ArticleList 元件

因為先前的調整,ArticleList 內幾乎已經沒有程式碼了,原因是該元件的 Tamplate 內是直接讀取 DataService 的資料。

但現在 DataService 已經沒有 atticleData 屬性了,怎麼辦呢?

解決辦法:

  • 呼叫 DataService 內的 getData 方法
    • 因為這個方法將回傳一個觀察者物件 (Observable Object),因此可以使用 .subscribe() 方法
      • 成功取得資料後,將資料賦值給 atticleData 屬性
        • 因為之前把元件內的 atticleData 屬性移除了,現在必須加回去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
constructor(public datasvc: DataService) {
}
ngOnInit() {
this.datasvc.getData().subscribe((result) => {
this.atticleData = result;
});
}
}

這樣看起來就合理多了,應該是由元件來決定何時訂閱,而不是由服務元件來決定。

因為可能會有多種不同的事件來處發 API ,所以由個別元件來決定是最合理的。

調整完後還有 Template 要修改:

  • 目前資料已經透過訂閱回到 ArticleList 本身了
    也因為資料已經在自身元件,所以刪除與改標題的方法也必須移動回元件本身。
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)="doDelete($event)" (changeTitle)="doModify($event)"></app-article-header>
<app-article-body [item]="item" [counter]="counter"></app-article-body>
</article>
<!-- Article END-->

最終 ArticleList 是這樣的:

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
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;
constructor(public datasvc: DataService) {
}
ngOnInit() {
this.datasvc.getData().subscribe((result) => {
this.atticleData = result;
});
}

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;
});
}
}

重新整理一下想法:

  • 目前 ArticleList 透過服務元件上的 getData() 方法取得 文章資料
    • 而現在的資料都是來自於 Server 端,我們只是把 Server 端的資料抓下來暫存在 atticleData 屬性而已
  • 因此 doDelete() 、 doModify() 都是修改存放在 local 端的資料,並不是真正修改 Server 端的資料

也就是說需要回到 DataService 上調整 doDelete() 、 doModify() 這兩個方法。

調整 DataService 服務元件

doDelete()

  • 使用 httpClient.delete() 發送一個 delete 請求給伺服器刪除特定資料,並帶上文章 id

doModify()

  • 使用 httpClient.put() 發送一個 put 請求給伺服器更新特定資料,並帶上文章 id
    • 第二個參數則傳入 post , httpClient 會自動把 post 這個物件轉換成 json 格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(public httpClient: HttpClient) {
}
getData() {
return this.httpClient.get('http://localhost:4200/api/articles.json');
}
doDelete(item) {
return this.httpClient.delete(`http://localhost:4200/api/articles.json/${item.id}`);
}
doModify(post: any) {
return this.httpClient.put(`http://localhost:4200/api/articles.json/${post.id}`, post);
}
}

至此 DataService 就調整完畢了,最後再次回到 ArticleList 元件完成真正的刪除與修改!

觸發 DataService 上的 doDelete() 及 doModify()

用法是這樣的:

  • 當 ArticleList 元件內的 doDelete() 被觸發時
    • 呼叫 DataService 上的 doDelete() 並傳入 item 並且訂閱結果
      • 當結果成立時 (伺服器端的資料被刪除了) 才執行 local 端的資料刪除

同理 doModify() 也是。

另外,串接 API 時常常會遇到一些意外的狀況,此時

  • 可以用上 .subscribe() 第二個參數 error ,同樣也是個 callback function
    • 可以透過這個 callback 取得錯誤的結果
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
37
38
39
40
41
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;
constructor(public datasvc: DataService) {
}
ngOnInit() {
this.datasvc.getData().subscribe((result) => {
this.atticleData = result;
});
}

doDelete(item) {
this.datasvc.doDelete(item).subscribe((result) => {
this.atticleData = this.atticleData.filter(v => v.id !== item.id);
},
(error) => {
console.log('錯誤資訊', error);
});
}
doModify(post: any) {
this.datasvc.doModify(post).subscribe((result) => {
this.atticleData = this.atticleData.map((item) => {
if (post.id === item.id) {
return Object.assign({}, item, post);
}
return item;
});
},
(error) => {
console.log('錯誤資訊', error);
});
}
}

大功告成,來試試看吧!

順利取得全部資料

進行刪除時因為找不到這隻 API 所以報錯

進行編輯時因為找不到這隻 API 所以報錯

一切都符合我們的預期 :D

小結

這一系列的原始碼

0%