Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

New to both ngrx/Store and reducer. Basically, I have this 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; } 

The problem is within the reducer function, I do not know how to inject an injectable service I created in a different file and use the function within it. The id part - I need to generate a firebase push ID using function like this.threadService.generateID() ...

But since this is a function, I do not have a constructor to use DI and I have no idea how to get functions within threadService!

like image 689
Hugh Hou Avatar asked Jan 22 '17 21:01

Hugh Hou


People also ask

Can we have multiple stores in NgRx?

For reuse and separability we require (at least) two state stores that do not interact with each other. But we do need both stores to be active at the same time, and potentially accessed from the same components. Ngrx seems to be predicated on the assumption that there will only ever be one Store at once.

When should you not use NgRx?

When should you not use NgRx? Never use NgRx if your application is a small one with just a couple of domains or if you want to deliver something quickly. It comes with a lot of boilerplate code, so in some scenarios it will make your coding more difficult.


1 Answers

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_ACTION SEND_NEW_MESSAGE_RES_ACTION SEND_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.

like image 165
cartant Avatar answered Sep 21 '22 21:09

cartant