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!
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.