How to use other Angular2 service inside an ngrx/Store reducer? How to use other Angular2 service inside an ngrx/Store reducer? angular angular

How to use other Angular2 service inside an ngrx/Store reducer?


There is no mechanism for injecting services into reducers. Reducers are supposed to be pure functions.

Instead, you should use ngrx/effects - which is the mechanism for implementing action side-effects. Effects listens for particular actions, perform some side-effect and then (optionally) emit further actions.

Typically, you would split your action into three: the request; the success response; and the error response. For example, you might use:

SEND_NEW_MESSAGE_REQ_ACTIONSEND_NEW_MESSAGE_RES_ACTIONSEND_NEW_MESSAGE_ERR_ACTION

And your effect would look something like this:

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(),                // ...              }          };        }      });  }}

Rather than interacting with the service, your reducer would then be a pure function that would need only to handle the SEND_NEW_MESSAGE_RES_ACTION and SEND_NEW_MESSAGE_ERR_ACTION to do something appropriate with the success or error payloads.

Effects are observable-based, so incorporating synchronous, promise-based or observable-based services is straight forward.

There are some effects in the ngrx/example-app.

Regarding your queries in the comments:

The .map(toPayload) is just for convinience. toPayload is an ngrx function that exists so it can be passed to .map to extract the action's payload, that's all.

Calling a service that's observable-based is straight-forward. Typically, you'd do something like this:

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(),          // ...        }      }))    );}

Also, effects can be declared as functions returning Observable<Action> or as properties of type Observable<Action>. If you are looking at other examples, you are likely to come across both forms.


After thinking a while about this I came up with this idea: What if I have a service full of pure functions which I don't want to keep in a global variable outside of angular like this:

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

I would like to have it as a service so I can easily pass it in the application without anybody freaking out that I don't use dependency injection:

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

I can use ReflectiveInjector in order to get an instance of the service I need. Keep in mind that this injector is invoked before the main app is going live so it's really necessary to play nice and avoid keeping state in these services. And of course also because reducers really have to be pure (for your own sanity).

// <!> Play nice and use only services containing pure functionsvar injector = ReflectiveInjector.resolveAndCreate([FooBarService]);var fooBarService= injector.get(FooBarService);// Due to changes in ngrx4 we need to define our own action with payloadexport 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;    }}

Using this setup I can use three types of service FooBarDataService, FooBarMapsService and FooBarLogicService. Data service calls the webapi and provides observables from the state store with the results. Map service is used to map foos to bars and Logic service is used to add the business logic in a separate layer. This way I can have tiny controllers that are used only to glue objects together and serve them to the templates. Almost no logic in controllers. And as a final touch, the resolvers can provide the state store data in the routes thus abstracting away the state store completely.

More details about ReflexiveInjector here.