如何在ngrx/Store reducer中使用其他Angular2服务?

Hug*_*Hou 18 dependency-injection redux ngrx angular

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;
}
Run Code Online (Sandbox Code Playgroud)

问题出在reducer函数中,我不知道如何注入我在不同文件中创建的可注入服务并使用其中的函数.id部分 - 我需要使用像this.threadService.generateID()这样的函数生成firebase推送ID ...

但由于这是一个函数,我没有使用DI的构造函数,我不知道如何在threadService中获取函数!

car*_*ant 26

没有为减压器注入服务的机制.减速器应该是纯粹的功能.

相反,您应该使用ngrx/effects- 这是实现动作副作用的机制.效果侦听特定操作,执行一些副作用,然后(可选)发出进一步的操作.

通常,您会将操作拆分为三个:请求; 成功的反应; 和错误响应.例如,您可以使用:

SEND_NEW_MESSAGE_REQ_ACTION
SEND_NEW_MESSAGE_RES_ACTION
SEND_NEW_MESSAGE_ERR_ACTION
Run Code Online (Sandbox Code Playgroud)

你的效果看起来像这样:

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(),
                // ...
              }
          };
        }
      });
  }
}
Run Code Online (Sandbox Code Playgroud)

而不是与服务交互,您的reducer将是一个纯函数,只需要处理SEND_NEW_MESSAGE_RES_ACTIONSEND_NEW_MESSAGE_ERR_ACTION执行适当的成功或错误有效负载.

效果是基于可观察的,因此合并同步,基于承诺或基于可观察的服务是直截了当的.

有一些影响ngrx/example-app.

关于您在评论中的查询:

.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(),
          // ...
        }
      }))
    );
}
Run Code Online (Sandbox Code Playgroud)

此外,效果可以声明为返回的函数Observable<Action>或类型的属性Observable<Action>.如果您正在查看其他示例,则可能会遇到两种形式.

  • 是的,差不多.如果它是同步的,你可以在第一个片段中的`map`内的任何地方进行服务调用 - 你将有效负载映射到结果动作. (2认同)

Adr*_*isa 5

考虑了一段时间后,我想出了这个想法:如果我有一个充满纯函数的服务,我不想将其保留在 angular 之外的全局变量中,该怎么办:

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

我想将它作为一项服务,这样我就可以轻松地将它传递到应用程序中,而不会有人担心我不使用依赖注入:

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

我可以使用ReflectiveInjector它来获取我需要的服务的实例。请记住,此注入器在主应用程序上线之前被调用,因此确实有必要保持良好状态并避免在这些服务中保持状态。当然也因为减速器真的必须是纯的(为了你自己的理智)。

// <!> 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

使用此设置,我可以使用三种类型的服务FooBarDataServiceFooBarMapsService以及FooBarLogicService. 数据服务调用 webapi 并提供来自状态存储的 observables 和结果。Map服务用于将foos映射到bars,Logic服务用于在单独的层中添加业务逻辑。通过这种方式,我可以拥有仅用于将对象粘合在一起并将它们提供给模板的微型控制器。控制器中几乎没有逻辑。最后,解析器可以在路由中提供状态存储数据,从而完全抽象出状态存储。

更多详情请ReflexiveInjector 点击此处

  • 我倾向于不同意这种观察。我说过两次服务应该只有纯功能。所以你可以认为它们几乎是一个外部库,比如下划线或其他什么。我看没有副作用。仅在 web api 调用之后产生的状态。如果我有一个 web api 调用同时提供 foos 和 bar(或单独调用 foos 和 bar)怎么办?酒吧仍然会被视为副作用吗?干杯! (4认同)
  • 那么你的减速器,根据定义,不再是一个纯函数,因为它依赖于外部状态——服务。 (2认同)