[Angular2官方tutorial學習03]service,Observable data,Rxjs,asynchronous service,service in service

[Angular2官方tutorial學習03]service,Observable data,Rxjs,asynchronous service,service in service

官方的介紹有點文言文,但是對我來說,其實就是他把資料存取的部分獨立到另外一個資料夾(類似像是MVC的Model資料夾),讓整個程式碼的架構乾淨清潔,以下就開始介紹囉

@執行Angular CLI指令以新增一個服務, 服務名稱叫做hero

ng g s hero


然後打開檔案src\app\hero.service.ts,以下是預設的內容
內容中真正重要的部分就只有@Injectable注入的root關鍵字,是用來設定這個服務的可使用範圍,而root的意思當然是整個應用程式範圍都可以使用
實務上如果要縮小該服務的使用範圍,再google一下Injectable的設定相關資訊即可,沒必要現在全記

import { Injectable } from '@angular/core';

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

  constructor() { }
}


同樣一個檔案繼續編輯,並且在HeroService的constructor下面加入下列程式碼

getHeroes(): Hero[] {
    return HEROES;
  }


加入完畢之後長這樣子


@接著回到src/app/heroes/heroes.component.ts,把該import的加進去

import { HeroService } from '../hero.service';

而且原本的heros直接吃假資料heroes = HEROES;  
把他改成下面這樣,純粹宣告就好

heroes: Hero[];

然後把HeroesComponent的constructor改成下面這樣,準備要Inject the HeroService注入服務

constructor(private heroService: HeroService) { }

再來於HeroesComponent的constructor的下面再加入一個method,來呼叫這個service

getHeroes(): void {
  this.heroes = this.heroService.getHeroes();
}


然後還是在src\app\heroes\heroes.component.ts,請在ngOnInit(){}裡面呼叫服務以取得資料,如下
官網有稍微提醒ngOnInit()才是用來取得資料用的,constructor則是用來設定基本的class的設定檔喔,別放錯地方了!

ngOnInit() {
  this.getHeroes();
}


最後src\app\heroes\heroes.component.ts就會長的像是這樣


此時再次打開瀏覽器,你會發現跟之前完全沒兩樣,哈哈
不過的確把資料的存取獨立到Service去,架構上會比較好拉


@接著要把取得資料的動作改成非同步的:在上面呼叫Service且取得資料的方式並不是Ajax,因此需將他改寫成非同步的,想當然實務上可能在查詢資料的時候,畫面可能會有delay,下面步驟將會示範非同步的作法

@首先打開src/app/hero.service.ts,加入下列的import來使用Rxjs的函式庫以呼叫相關的非同步method

import { Observable, of } from 'rxjs';


然後把裡面原本的getHeros()這個method取代成如下。Observable就是Rxjs的關鍵字,他讓整個method變成非同步的

getHeroes(): Observable<Hero[]> {
  return of(HEROES);
}


然後再打開src\app\heroes\heroes.component.ts,把裡面原本宣告的getHeros(): void....改成如下
sbuscribe就是Rxjs的關鍵字,他讓整個呼叫的過程變成非同步

getHeroes(): void {
  this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes);
}


最後src\app\heroes\heroes.component.ts程式碼長這樣

最後src\app\hero.service.ts程式碼長這樣

再次打開瀏覽器發現沒什麼不一樣 XD
其實取得hero list的時候,已經改用非同步的了拉~


@接著再來練習稍微不同的service的運用,首先再次執行下列angular cli指令以新增另一個component

ng g c messages


產生component完畢之後,打開/src/app/app.component.html,請將原本的下列內容

<h1>{{title}}</h1>
<app-heroes></app-heroes>


修改成下面這樣,用來測試新產生的component

<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>


目前的進度下,打開瀏覽器會看到message component會位於畫面的最下方


然後再執行下列angular cli指令,新增另一個message service

ng g s message


打開/src/app/message.service.ts,將整個內容修改如下
add()是把資料push到變數陣列中(即把資料push到cache裡面),而clear()就是清空變數陣列(即清空cache)

import { Injectable } from '@angular/core';
 
@Injectable({
  providedIn: 'root',
})
export class MessageService {
  messages: string[] = [];
 
  add(message: string) {
    this.messages.push(message);
  }
 
  clear() {
    this.messages = [];
  }
}


接著打開/src/app/hero.service.ts,加入下列import,我們打算在hero service裡面呼叫message service

import { MessageService } from './message.service';


在同一個檔案,把constructor改成如下,表示要message service及將要在hero service裡面呼叫

constructor(private messageService: MessageService) { }


同一個檔案裡面,再去修改getHeroes()這個method,return的部分不變,但是額外多了this.messageService.add('HeroService: fetched heroes');
把一段訊息加入到message service的cache裡面了

getHeroes(): Observable<Hero[]> {
  // TODO: send the message _after_ fetching the heroes
  this.messageService.add('HeroService: fetched heroes');
  return of(HEROES);
}


再來打開src/app/messages/messages.component.html,修改整個內容如下,這是用來顯示所有在message service裡面的變數陣列

<div *ngIf="messageService.messages.length">

  <h2>Messages</h2>
  <button class="clear"
          (click)="messageService.clear()">clear</button>
  <div *ngFor='let message of messageService.messages'> {{message}} </div>

</div>


最後這個步驟最重要,請打開/src/app/messages/messages.component.ts,並加入下列import以及修改constructor
因為剛才我們在src/app/messages/messages.component.html裡面,直接使用message service,因此必須在下面的constructor裡面宣告message service為public

import { MessageService } from '../message.service';
constructor(public messageService: MessageService) {}


我們回頭打開src\app\heroes\heroes.component.html來比較一下,這邊他只有使用變數,並非直接使用hero service


也因此在src\app\heroes\heroes.component.ts裡面的constructor無須宣告hero service為public


大概是這樣……下面是瀏覽器目前的畫面,雖然不重要 XD
但是還是貼一下截圖吧


目前為止的進度的整個專案檔提供給大家下載囉,放在dropbox
https://www.dropbox.com/s/1em2z6czkhsxiah/angular-tour-of-heros%20-%20%E5%AE%8C%E6%88%90service.rar?dl=0


參考資料:
[Angular2官方tutorial學習02]display list,master detail,click event,master detail advanced,幫css設定加上if
https://dotblogs.com.tw/kevinya/2018/10/26/105353
Services - Angular official tutorial
https://angular.io/tutorial/toh-pt4