almost 2 years ago

前面幾個章節風風火火的介紹如何編寫一個簡單的Angular2 App,也做出了一個雛形了。
但是還缺少最重要 與Remoted Server交互的Http Request功能,Angular2提供了Http這個核心模組重新封裝了XmlHttpRequest(XHR)和JSONP 主流 browser/server溝通方式,使用方式透過 RxJS提供的Observable與Subscribe。
以下是操作影片
在Hero中有將資料同步到Server上,而Task沒有,在操作上可以看出差異。

RxJS - Observable 與 Subscribe

先附上幾個文件與教學 RxJS and Observables with Angular 2 英文影片教學Reactive Programming 簡介與教學(以 RxJS 為例)Creating and Subscribing to Simple Observable Sequence 英文教學官網 ReactiveX,個人建議先閱讀中文文件,然後看英文教學影片,官網則有空再看,因為官網文件爆多爆長的..... 雖然我看完兩遍了XD
當初看到RxJS有種Mind Blowing的感覺,他主要提倡 非同步資料流的概念,在JS事件中,處理非同步事件十分常見,像是最基本的Promise,但是Promise幾個特性是 單次、不可取消(官方的不行);但是在網站實際應用上,我們常常需要 長時間反覆的觀察某個資料狀態的變化,並且在畫面跳轉時取消關注增加效能;
在這種情況下傳統的Promise就不太好用了,而符合這種特性的原生API只有 addEventListener但又只能綁定DOM Element,此時就是應用RxJS的大好機會了。

在RxJS中,有兩個角色,Observable和Subscription,Observable負責產生資料,創建後不會馬上啟動,而且只有存在Subscription關注(subscribe)才會開始。
在subscribe中可以放入三個函式: (成功收到資料)、(收到Error時)、(收到結束訊號時)。
以下是我複製英文教學中的範例

//創建被觀察者

var source = Rx.Observable.create(observer => {
  // 透過onNext產生資料

  observer.onNext(42);
  //發出結束訊號

  observer.onCompleted();

  // Any cleanup logic might go here

  return () => console.log('disposed')
});
//創建關注

var subscription = source.subscribe(
  x => console.log('onNext: %s', x),
  e => console.log('onError: %s', e),
  () => console.log('onCompleted'));
)
//有人關注後,如果一直觸發observer.onNext(data),則subscription就會不斷的打印'onNext:data'


//取消關注

source.unsubscribe()

創建Observable可以參考官網,其中Angular2的HTTP Module本身就是Observable。
另外關於資料流部分,RxJS將資料視為長時間但不連續(discrete)的資料流,依此也提供大量API來處理,例如filter:過濾、map:對每個值做處理、debounce:限制時間內只產出一個值、json:將資料轉為json格式等等,API實在是太多了只能自己慢慢看。

HTTP 實戰

在使用HTTP上相當直覺,首先要到Module中import HttpModule,接著再要使用http的component或是service中的contructor注入(http:Http)
這裡我希望Hero的資料可以放入資料庫,所以到hero.service.ts中定義取得英雄與新增英雄

constructor(private http:Http){...}
private dbURL = "https://test2-25905.firebaseio.com/heros.json";
private headers = new Headers({ 'Content-Type': 'application/json' });
private options = new RequestOptions({ headers: this.headers });
    
getHerosHttp():Observable<any>{
        return this.http.get(this.dbURL,this.options).map((res: Response) => {
            let body = res.json();
            return body || { };
        });
    }
createHero(newHero:Hero):Observable<any>{
        return this.http.post(this.dbURL,JSON.stringify(newHero),this.options).map((res: Response) => {
            let body = res.json();
            return body || { };
        });
    }

通常在使用HTTP時,都會在Service中宣告(畢竟是專門處理資料的地方),接著用一個函示包住,最後回傳相對應的Http method Observable,Gethttp.get(//url, //options - 放header及其他設定)POST則是多一個挾帶的資料:http.get(//url, // data, //options - 放header及其他設定);至於options的其他常見設定還包括 CORS中夾帶Cookie let options = new RequestOptions({ headers: headers, withCredentials: true });
最後面加入map則是因為我希望可以先將資料格式轉為json()
如一般的Observable,宣告完是不會被觸發的,所以必須使用subscribe

this.subscription = this.getHerosHttp().subscribe((data)=>{
    for(var i in data){
        data[i].id = i;
        this.heroList.push(new Hero(data[i].name, data[i].age, data[i].money, data[i].imgUrl, data[i].address, data[i].id));
        console.log(this.heroList[0].taskList);
    }
},(err)=>{
    console.error("Get Heros Err: " + err);
});

有趣的是 我個人是覺得AJAX是不太需要持續關注,畢竟通常都是單次就回收,所以可以利用RxJS官方內建的toPromise再將Observable轉回Promise this.http.get(this.heroesUrl).toPromise()

這樣就完成最基本的HTTP Request in Angular2。

Pipe

Pipe主要是指 將資料管道化,如果有用過*unix指令都應該熟悉cat index.html | grep html這類型將上一個指令的Output當作下一個指令的input 將資料串連下去,Pipe也是相同的概念。
因為我個人很少在使用Pipe,所以以下內容從官網PIPES整理。
可以看到字串內插中出現了| date:"MM/dd/yy" 這表示將birthday的日期顯示格式轉為後者的格式

<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
//也可以接多個Pipe
{{ birthday | date | uppercase}}

其餘支援的內建Pipe包含 uppercase:轉大寫、currency:轉貨幣格式、slice:1:5:取字串[1:5]位元等

當然也可以自行宣告Pipe,同樣使用Pipe Decorator

@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
  transform(value: number, exponent: string): number {
    let exp = parseFloat(exponent);
    return Math.pow(value, isNaN(exp) ? 1 : exp);
  }
}

Pipe class中必須包含transform函式,參數value為傳入Pipe的Input、exponent表示傳入Pipe的變數,而return回去的字串就是會顯示的值囉。以下為範例

<p>Super power boost: {{2 | exponentialStrength: 10}}</p>

這時候value就是2,exponent就是10。

Pipe的Change Detection

在沒有使用Pipe的時候,Angular2會採取積極的Change Detection,如下方有個 新增Hero的地方,同時有個ngFor展開所有Hero,假設此時是使用push的方式加入新英雄(也就是heroes這個Array Reference沒有改變),此時畫面依然會更新。

New hero:
  <input type="text" #box
          (keyup.enter)="addHero(box.value); box.value=''"
          placeholder="hero name">
  <button (click)="reset()">Reset</button>
  <div *ngFor="let hero of heroes">
    {{hero.name}}
  </div>

假設現在新增了一個Pipe,找出會飛的英雄,此時如果新增了英雄或是增加原英雄飛行的能力,畫面都不會更新!
原因是對於Pipe Angular2採取不同的Change Detection(It's just using a different change detection algorithm — one that ignores changes to the list or any of its items.)
如果發現物件的Reference沒有改變,Angular2就當作沒有改變! 還記得先前有提過的Mutable(物件與陣列)和Immutable(基本型態)嗎?這裡就是體現兩者差別的最好時機,關於Change Detection會再補充!

<div *ngFor="let hero of (heroes | flyingHeroes)">
  {{hero.name}}
</div>

@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
  transform(allHeroes: Flyer[]) {
    return allHeroes.filter(hero => hero.canFly);
  }
}

如果希望Pipe採取積極的改變偵測,就必須宣告為Impure

@Pipe({
  name: 'flyingHeroesImpure',
  pure: false
})

加入後,剛才的問題就解決囉~ 但是要注意,Impure Pipe觸發狀態偵測的頻率就跟滑鼠移動一樣!所以要特別特別小心使用(An impure pipe will be called a lot, as often as every keystroke or mouse-move.)

Async Pipe

Angular2支援非同步Pipe,以下範例是 將message$綁定給async,而在async之中的Observable則是每隔500ms(internal(500))發出一個message。

import { Component } from '@angular/core';
import { Observable } from 'rxjs/Rx';
@Component({
  selector: 'hero-message',
  template: `
    <h2>Async Hero Message and AsyncPipe</h2>
    <p>Message: {{ message$ | async }}</p>
    <button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
  message$: Observable<string>;
  private messages = [
    'You are my hero!',
    'You are the best hero!',
    'Will you be my hero?'
  ];
  constructor() { this.resend(); }
  resend() {
    this.message$ = Observable.interval(500)
      .map(i => this.messages[i])
      .take(this.messages.length);
  }
}

官網後面還有附幾個解析JSON和進階的應用,有興趣可以看看。
最後官網有提到說 為什麼沒有提供filtersorting的Pipe,原因就在先前的Impure Pipe,因為要不斷偵測改變很耗性能,而過濾與排序也是耗性能的功能,所以就不提供囉~ 建議可以在原資料產生時自己過濾或排序。

總結
- Observable基本用法,Subscibe基本用法((data)=>{}, (err)=>{}, {}=>{'Complete!'})
- HTTP基本用法
- Pipe使用,包含自定義、Async Pipe和Impure Pipe

基本上,所有Angular2基本操作都講解完了,只要熟悉基本的元件,就可以打造自己的App;但是目前的性能有點低落,而且只能跑在local server,下一章談如何上Production並deploy到(Free)Server上

← Angular2系列文七-Routing進階篇 Scalable CSS - 介紹OOCSS/SMACSS/BEM →
 
comments powered by Disqus