ngRx 官方示例分析 - 6 - Effect


 

@ngrx/effect 

前面我們提到,在 Book 的 reducer 中,並沒有 Search 這個 Action 的處理,由於它需要發出一個異步的請求,等到請求返回前端,我們需要根據返回的結果來操作 store。所以,真正操作 store 的應該是 Search_Complete 這個 Action。我們在 recducer 已經看到了。

對於 Search 來說,我們需要見到這個 Action 就發出一個異步的請求,等到異步處理完畢,根據返回的結果,構造一個 Search_Complete 來將處理的結果派發給 store 進行處理。

這個解耦是通過 @ngrx/effect 來處理的。

@ngrx/effect 提供了裝飾器 @Effect 和 Actions 來幫助我們檢查 store 派發出來的 Action,將特定類型的 Action 過濾出來進行處理。監聽特定的 Action, 當發現特定的 Action 發出之后,自動執行某些操作,然后將處理的結果重新發送回 store 中。

Book 搜索處理 

以 Book 為例,我們需要監控 Search 這個 Action, 見到這個 Action 就發出異步的請求,然后接收請求返回的數據,如果在接收完成之前,又遇到了下一個請求,那么,直接結束上一個請求的返回數據。如何找到下一個請求呢?使用 skip 來獲取下一個 Search Action。

源碼

/src/effects/book.ts

import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/skip';
import 'rxjs/add/operator/takeUntil';
import { Injectable } from '@angular/core';
import { Effect, Actions, toPayload } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { empty } from 'rxjs/observable/empty';
import { of } from 'rxjs/observable/of';

import { GoogleBooksService } from '../services/google-books';
import * as book from '../actions/book';


/**
 * Effects offer a way to isolate and easily test side-effects within your
 * application.
 * The `toPayload` helper function returns just
 * the payload of the currently dispatched action, useful in
 * instances where the current state is not necessary.
 *
 * Documentation on `toPayload` can be found here:
 * https://github.com/ngrx/effects/blob/master/docs/api.md#topayload
 *
 * If you are unfamiliar with the operators being used in these examples, please
 * check out the sources below:
 *
 * Official Docs: http://reactivex.io/rxjs/manual/overview.html#categories-of-operators
 * RxJS 5 Operators By Example: https://gist.github.com/btroncone/d6cf141d6f2c00dc6b35
 */

@Injectable()
export class BookEffects {

  @Effect()
  search$: Observable<Action> = this.actions$
    .ofType(book.ActionTypes.SEARCH)
    .debounceTime(300)
    .map(toPayload)
    .switchMap(query => {
      if (query === '') {
        return empty();
      }

      const nextSearch$ = this.actions$.ofType(book.ActionTypes.SEARCH).skip(1);

      return this.googleBooks.searchBooks(query)
        .takeUntil(nextSearch$)
        .map(books => new book.SearchCompleteAction(books))
        .catch(() => of(new book.SearchCompleteAction([])));
    });

    constructor(private actions$: Actions, private googleBooks: GoogleBooksService) { }
}

 collection.ts

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/toArray';
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects';
import { Database } from '@ngrx/db';
import { Observable } from 'rxjs/Observable';
import { defer } from 'rxjs/observable/defer';
import { of } from 'rxjs/observable/of';

import * as collection from '../actions/collection';
import { Book } from '../models/book';


@Injectable()
export class CollectionEffects {

  /**
   * This effect does not yield any actions back to the store. Set
   * `dispatch` to false to hint to @ngrx/effects that it should
   * ignore any elements of this effect stream.
   *
   * The `defer` observable accepts an observable factory function
   * that is called when the observable is subscribed to.
   * Wrapping the database open call in `defer` makes
   * effect easier to test.
   */
  @Effect({ dispatch: false })
  openDB$: Observable<any> = defer(() => {
    return this.db.open('books_app');
  });

  /**
   * This effect makes use of the `startWith` operator to trigger
   * the effect immediately on startup.
   */
  @Effect()
  loadCollection$: Observable<Action> = this.actions$
    .ofType(collection.ActionTypes.LOAD)
    .startWith(new collection.LoadAction())
    .switchMap(() =>
      this.db.query('books')
        .toArray()
        .map((books: Book[]) => new collection.LoadSuccessAction(books))
        .catch(error => of(new collection.LoadFailAction(error)))
    );

  @Effect()
  addBookToCollection$: Observable<Action> = this.actions$
    .ofType(collection.ActionTypes.ADD_BOOK)
    .map((action: collection.AddBookAction) => action.payload)
    .mergeMap(book =>
      this.db.insert('books', [ book ])
        .map(() => new collection.AddBookSuccessAction(book))
        .catch(() => of(new collection.AddBookFailAction(book)))
    );


  @Effect()
  removeBookFromCollection$: Observable<Action> = this.actions$
    .ofType(collection.ActionTypes.REMOVE_BOOK)
    .map((action: collection.RemoveBookAction) => action.payload)
    .mergeMap(book =>
      this.db.executeWrite('books', 'delete', [ book.id ])
        .map(() => new collection.RemoveBookSuccessAction(book))
        .catch(() => of(new collection.RemoveBookFailAction(book)))
    );

    constructor(private actions$: Actions, private db: Database) { }
}

  

See also:

@ngrx/effects

skip

takeUntil

What is the purpose of ngrx effects library?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM