Angular5.0 的事件訂閱

Angular 範例程式中,最有名(?)的除了官網的 Hero 之外,還有 TodoMVC

進入到 Angular5.0 之後,當然也不能免俗的要再改寫一次。

在 Angular5.0,最重要的改變就是引進了 RxJS 的 Observable 用法;

也就是 Observable(被觀察者) / Observer(觀察者)。

觀察者樣式最常見的實作範例就是 “事件” ,
在程式中可以訂閱按鈕的 click 事件 – Observer(觀察者)
按鈕被按下之後,clickEvent 會被拋出 – 按鈕是 Observable(被觀察者)

在 Angular4.0裡,是使用 Promoise 來實作非同步的資料提供者 - TodoService,

以下是4.0寫法:(範例僅保留關鍵,非完整程式)

src/app/todo/todo.service.ts


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

@Injectable()
export class TodoService {
    private todos: ToDo[] = [];

    constructor() { }

    get() {
        return new Promise(resolve => resolve(todos));
    }
}

src/app/todo/todo.component.ts

import { Component, OnInit } from '@angular/core';
import { TodoService } from './todo.service';
export class TodoComponent implements OnInit {
  private todos;

  constructor(private todoService: TodoService) { }

  getTodos(){
    return this.todoService.get().then(todos => {
      this.todos = todos;
    });
  }
  ngOnInit() {
    this.getTodos();
  }
}

到了 5.0,改用 Observable 的寫法是:

TodoService src/app/todo/todo.service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject } from "rxjs";
import { Observable } from "rxjs/Observable";

@Injectable()
export class TodoService {
    private todos: ToDo[] = [];
    private lastId: number = 0;
    private _todos: BehaviorSubject<ToDo[]>;

    constructor() { }

    get(): Observable<ToDo[]> {
        return this._todos;
    }

    add(data: ToDo) {
        this.todos.push(data);

        this._todos.next(this.todos);
    }
}

觀察者訂閱事件方式:

src\app\todo\todo.component.ts

import { TodoService } from ‘./todo.service’;
export class TodoComponent implements OnInit { todos: ToDo[]; constructor(private toDoService: TodoService) { } ngOnInit() { this.getTodos(); } getTodos() { this.toDoService.get().subscribe( { next: (todos) => { this.todos = todos; } } ); } }


RxJS 的被觀察者有 3 種通知狀態:
1. “next”:發送目前數值;內容可為 number / string/ object…;觀察者可以透過此狀態取得新內容
2. “error”:發送錯誤內容
3. “complete”:通知已經執行完成,不會再發送新的數值;如果需要知道執行的工作何時完成,可以訂閱此狀態
觀察者也是透過同名狀態取得資料。

RxJS 裡的 Observable 有 4 種類型:
1. Subject:最基本的 Observable ,可有多個觀察者。
2. BehaviorSubject:Subject 的變形,內部會保留最新狀態,當有新的觀察者訂閱時,會立刻發送目前狀態。若使用 Subject ,在被觀察者送出狀態後才訂閱的觀察者,已經錯過之前狀態的通知,只能等到下次通知時才會獲得被觀察者的狀態。

從範例來看:
import { BehaviorSubject } from 'rxjs';
var subject = new BehaviorSubject(0); // 需要給初始值

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
// print: observerA: 0  // 雖然還沒有數值變化,訂閱立刻就會收到當下最新狀態

subject.next(1);
// print: observerA: 1
subject.next(2);
// print: observerA: 2

subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

subject.next(3);
// print: observerA: 3
// print: observerB: 3   // 訂閱當下 B 就會收到當下最新狀態

也來看看 Subject 的範例:

import { Subject } from ‘rxjs’; 
var subject = new Subject();
subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});

subject.next(1);
// print: observerA: 1

subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

subject.next(2);
// print: observerA: 2
// print: observerB: 2  // 只會收到訂閱後的數值變化

3 . ReplaySubject:BehaviorSubject 的變形,不僅後到的觀察者會取得先前已發送過的狀態,根據 buffer 長度,可以獲得前 N 次的狀態。

ReplaySubject() { 
    const subject = new ReplaySubject(2);   // 宣告儲存舊狀態的 buffer 長度為 2
    subject.subscribe({
      next: (v) => { 
        console.log(‘observerA: ’ + v)
      } 
    });


subject.next(1);
// print: observerA: 1
subject.next(2);
// print: observerA: 2
subject.next(3);
// print: observerA: 3

subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});
// print: observerB: 2
//           observerB: 3   // 因為 buffer 長度為 2 ,會記錄前兩次的狀態,發送給新訂閱者

subject.next(4);
// print: observerA: 4
//           observerB: 4
}
4. AsyncSubject:非同步的通知者;只有當工作完成後,觀察者才會收到通知。
var subject = new AsyncSubject();


subject.subscribe({
  next: (v) => console.log(‘observerA: ’ + v) 
});


subject.next(1); 
subject.next(2);


subject.subscribe({ 
next: (v) => console.log(‘observerB: ’ + v) }); subject.next(3); subject.complete(); // print: observerA: 3 // observerB: 3

以上範例來自 RxJS 官網

在 ToDo 範例中,選擇使用 BehaviorSubject。

以上解釋,如有錯誤或不清楚的地方,歡迎指教!

留言