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!
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? 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With