Как использовать другую службу Angular2 внутри редуктора ngrx/Store?
Новое для ngrx/Store и редуктора. В принципе, у меня есть этот редуктор:
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;
}
Проблема заключается в функции редуктора, я не знаю, как вводить инъекционную услугу, которую я создал в другом файле, и использовать функцию внутри нее. Часть id - мне нужно сгенерировать идентификатор push-кода firebase, используя такую функцию, как this.threadService.generateID()...
Но так как это функция, у меня нет конструктора для использования DI, и я понятия не имею, как получить функции в threadService!
Ответы
Ответ 1
В редукторах нет механизма для инъекций услуг. Редукторы должны быть чистыми функциями.
Вместо этого вы должны использовать ngrx/effects
- это механизм для реализации побочных эффектов действий. Эффекты прослушивают определенные действия, выполняют некоторый побочный эффект, а затем (необязательно) выделяют дальнейшие действия.
Как правило, вы разделили свое действие на три: запрос; ответ успеха; и ответ об ошибке. Например, вы можете использовать:
SEND_NEW_MESSAGE_REQ_ACTION
SEND_NEW_MESSAGE_RES_ACTION
SEND_NEW_MESSAGE_ERR_ACTION
И ваш эффект будет выглядеть примерно так:
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(),
// ...
}
};
}
});
}
}
Вместо того, чтобы взаимодействовать с сервисом, ваш редуктор будет тогда чистой функцией, которая должна обрабатывать только теги SEND_NEW_MESSAGE_RES_ACTION
и SEND_NEW_MESSAGE_ERR_ACTION
, чтобы сделать что-то подходящее с полезной нагрузкой успеха или ошибки.
Эффекты основаны на наблюдениях, поэтому включение синхронных, основанных на обещаниях или наблюдаемых сервисов является прямым.
В ngrx/example-app
есть effects.
Относительно ваших запросов в комментариях:
.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(),
// ...
}
}))
);
}
Кроме того, эффекты могут быть объявлены как функции, возвращающие Observable<Action>
или как свойства типа Observable<Action>
. Если вы смотрите на другие примеры, вы, вероятно, столкнетесь с обеими формами.
Ответ 2
Подумав об этом, я придумал эту идею: что, если у меня есть сервис, полный чистых функций, которые я не хочу хранить в глобальной переменной вне angular следующим образом:
export const fooBarService= {
mapFooToBar: (foos: Foo[]): Bar[] => {
let bars: Bar[];
// Implementation here ...
return bars;
}
}
Я хотел бы иметь это как сервис, поэтому я могу легко передать его в приложении, не обращая внимания на то, что я не использую инъекцию зависимостей:
@Injectable()
export class FooBarService{
public mapFooToBar (foos: Foo[]): Bar[] {
let bars: Bar[];
// Implementation here ...
return bars;
}
}
Я могу использовать 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;
}
}
Используя эту настройку, я могу использовать три типа сервисов FooBarDataService
, FooBarMapsService
и FooBarLogicService
. Служба данных вызывает webapi и предоставляет результаты из хранилища состояния с результатами. Служба карты используется для сопоставления foos с барами, а служба Logic используется для добавления бизнес-логики в отдельный слой. Таким образом, у меня могут быть крошечные контроллеры, которые используются только для склеивания объектов и обслуживания их к шаблонам. Практически нет логики в контроллерах. И в качестве последнего штриха резольверы могут предоставлять данные хранилища данных на маршрутах, тем самым полностью абстрагируя государственный магазин.
Подробнее о ReflexiveInjector
здесь.