web-dev-qa-db-ja.com

ngrx / Storeレデューサー内で他のAngular2サービスを使用するには?

Ngrx/Storeとreducerの両方に新しい。基本的に、私はこの減速機を持っています:

import {StoreData, INITIAL_STORE_DATA} from "../store-data";
import {Action} from "@ngrx/store";
import {
  USER_THREADS_LOADED_ACTION, UserThreadsLoadedAction, SEND_NEW_MESSAGE_ACTION,
  SendNewMessageAction
} from "../actions";
import * as _ from "lodash";
import {Message} from "../../shared-vh/model/message";
import {ThreadsService} from "../../shared-vh/services/threads.service";

export function storeData(state: StoreData = INITIAL_STORE_DATA, action: Action): StoreData {


  switch (action.type) {

    case SEND_NEW_MESSAGE_ACTION:
      return handleSendNewMessageAction(state, action);

    default:
      return state
  }
}

function handleSendNewMessageAction(state:StoreData, action:SendNewMessageAction): StoreData {

  const newStoreData = _.cloneDeep(state);

  const currentThread = newStoreData.threads[action.payload.threadId];

  const newMessage: Message = {
    text: action.payload.text,
    threadId: action.payload.threadId,
    timestamp: new Date().getTime(),
    participantId: action.payload.participantId,
    id: [need a function from this service: ThreadsService]
  }

  currentThread.messageIds.Push(newMessage.id);

  newStoreData.messages[newMessage.id] = newMessage;

  return newStoreData;
}

問題はリデューサー関数内にあります。別のファイルで作成した注入可能なサービスを注入し、その中で関数を使用する方法はわかりません。 id部分-this.threadService.generateID()などの関数を使用してfirebaseプッシュIDを生成する必要があります...

しかし、これは関数であるため、DIを使用するコンストラクターがなく、threadService内で関数を取得する方法がわかりません。

17
Hugh Hou

レデューサーにサービスを注入するメカニズムはありません。レデューサーは純粋な関数であると想定されています。

代わりに、 _ngrx/effects_ を使用する必要があります。これは、アクションの副作用を実装するためのメカニズムです。エフェクトは特定のアクションをリッスンし、何らかの副作用を実行してから、(オプションで)さらにアクションを発行します。

通常、アクションは3つに分割されます。リクエスト、成功応答;およびエラー応答。たとえば、次を使用できます。

_SEND_NEW_MESSAGE_REQ_ACTION
SEND_NEW_MESSAGE_RES_ACTION
SEND_NEW_MESSAGE_ERR_ACTION
_

そして、あなたの効果は次のようになります。

_import { Injectable } from "@angular/core";
import { Actions, Effect, toPayload } from "@ngrx/effects";
import { Action } from "@ngrx/store";
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/map";

@Injectable()
export class ThreadEffects {

  constructor(
    private actions: Actions,
    private service: ThreadsService
  ) {}

  @Effect()
  sendNewMessage(): Observable<Action> {

    return this.actions
      .ofType(SEND_NEW_MESSAGE_REQ_ACTION)
      .map(toPayload)
      .map(payload => {
        try {
          return {
              type: SEND_NEW_MESSAGE_RES_ACTION,
              payload: {
                  id: service.someFunction(),
                  // ...
              }
          };
        } catch (error) {
          return {
              type: SEND_NEW_MESSAGE_ERR_ACTION
              payload: {
                error: error.toString(),
                // ...
              }
          };
        }
      });
  }
}
_

レデューサーは、サービスと対話するのではなく、成功またはエラーペイロードに対して適切な処理を行うために_SEND_NEW_MESSAGE_RES_ACTION_および_SEND_NEW_MESSAGE_ERR_ACTION_を処理するだけの純粋な関数になります。

効果は観測ベースであるため、同期、約束ベース、または観測ベースのサービスを組み込むことは簡単です。

_ngrx/example-app_には effects がいくつかあります。

コメント内のクエリについて:

.map(toPayload)は便宜上のものです。 toPayloadは、存在するngrx関数であるため、_.map_に渡して、アクションのpayloadを抽出できます。

オブザーバブルベースのサービスの呼び出しは簡単です。通常、次のようなことをします。

_import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/of";
import "rxjs/add/operator/catch";
import "rxjs/add/operator/map";
import "rxjs/add/operator/switchMap";

@Effect()
sendNewMessage(): Observable<Action> {

  return this.actions
    .ofType(SEND_NEW_MESSAGE_REQ_ACTION)
    .map(toPayload)
    .switchMap(payload => service.someFunctionReturningObservable(payload)
      .map(result => {
        type: SEND_NEW_MESSAGE_RES_ACTION,
        payload: {
          id: result.id,
          // ...
        }
      })
      .catch(error => Observable.of({
        type: SEND_NEW_MESSAGE_ERR_ACTION
        payload: {
          error: error.toString(),
          // ...
        }
      }))
    );
}
_

また、効果は_Observable<Action>_を返す関数または_Observable<Action>_型のプロパティとして宣言できます。他の例を見ると、両方のフォームに出くわす可能性があります。

25
cartant

これについてしばらく考えた後、私はこのアイデアを思いつきました:angularのようなグローバル変数に保持したくない純粋な関数でいっぱいのサービスがある場合はどうですか:

export const fooBarService= {
    mapFooToBar: (foos: Foo[]): Bar[] => {
        let bars: Bar[];
        // Implementation here ...
        return bars;
    } 
}

私は依存関係の注入を使用していないことをだれも恐れずに簡単にアプリケーションに渡すことができるように、サービスとしてそれを持ちたいです:

@Injectable()
export class FooBarService{
    public mapFooToBar (foos: Foo[]): Bar[] {
        let bars: Bar[];
        // Implementation here ...
        return bars;
    } 
}

必要なサービスのインスタンスを取得するために、ReflectiveInjectorを使用できます。このインジェクターはメインアプリが公開される前に呼び出されるため、Niceをプレイし、これらのサービスの状態を維持しないようにする必要があります。そしてもちろん、減速機は本当に純粋でなければならないからです(あなた自身の正気のために)。

// <!> Play Nice and use only services containing pure functions
var injector = ReflectiveInjector.resolveAndCreate([FooBarService]);
var fooBarService= injector.get(FooBarService);

// Due to changes in ngrx4 we need to define our own action with payload
export interface PayloadAction extends Action {
    payload: any
}

/**
 * Foo bar reducer
 */
export function fooBarReducer(
    state: FooBarState = initialState.fooBar, 
    action: PayloadAction
) {
    switch (action.type) {

        case fooBarActions.GET_FOOS_SUCCESS:
            return Object.assign({}, state, <FooBarState>{
                foos: action.payload,
                // No effects used, all nicelly done in the reducer in one shot
                bars: fooBarService.mapFooToBar (action.payload) 

            });

        default:
            return state;
    }

}

このセットアップを使用して、3つのタイプのサービスFooBarDataServiceFooBarMapsService、およびFooBarLogicServiceを使用できます。データサービスはwebapiを呼び出し、状態ストアからオブザーバブルに結果を提供します。マップサービスはfooをバーにマップするために使用され、ロジックサービスはビジネスロジックを別のレイヤーに追加するために使用されます。これにより、オブジェクトをつなぎ合わせてテンプレートに提供するためだけに使用される小さなコントローラーを使用できます。コントローラにはほとんどロジックがありません。最後の仕上げとして、リゾルバーは状態ストアのデータをルートに提供し、状態ストアを完全に抽象化します。

ReflexiveInjectorhere の詳細。

2
Adrian Moisa