ngrx-reducer-effects
Version:
Return side-effects as data from your NgRx reducers
205 lines (193 loc) • 7.79 kB
JavaScript
import { InjectionToken, Injector, NgModule } from '@angular/core';
import { Store, compose, StoreModule, META_REDUCERS, USER_PROVIDED_META_REDUCERS, combineReducers, REDUCER_FACTORY } from '@ngrx/store';
import { isObservable, Subscription } from 'rxjs';
function createReducerEffect(effectCreator) {
return (params) => Object.assign(typeof effectCreator === 'function' ? effectCreator(params) : effectCreator, { params });
}
const stateWithEffectsBrand = 'StateWithEffects';
function withEffects(state, ...effects) {
return {
__brand: stateWithEffectsBrand,
state,
effects
};
}
const unsubscribeBrand = 'Unsubscribe';
function unsubscribe(subscriptionToken) {
return {
__brand: unsubscribeBrand,
subscriptionToken
};
}
const handler = {
get() {
return proxy;
}
};
const target = {};
const proxy = new Proxy(target, handler);
function addEffectDescriptions(state, effects) {
const effectDescriptions = effects.map((effect) => ({
type: effect.type,
params: effect.params,
nextAction: hasNextAction(effect) ? effect.next(proxy).type : undefined,
errorAction: hasErrorAction(effect) ? effect.error(proxy).type : undefined,
completeAction: hasCompleteAction(effect) ? effect.complete().type : undefined,
subscribeAction: hasSubscribeAction(effect)
? effect.subscribe(0).type
: undefined,
resolveAction: hasResolveAction(effect) ? effect.resolve(proxy).type : undefined,
rejectAction: hasRejectAction(effect) ? effect.reject(proxy).type : undefined,
unsubscribeAction: hasUnsubscribeAction(effect)
? effect.unsubscribe(0).type
: undefined
}));
return Object.assign(state, { __effects__: effectDescriptions });
}
function hasNextAction(effect) {
return effect.next !== undefined;
}
function hasErrorAction(effect) {
return effect.error !== undefined;
}
function hasCompleteAction(effect) {
return effect.complete !== undefined;
}
function hasSubscribeAction(effect) {
return effect.subscribe !== undefined;
}
function hasResolveAction(effect) {
return effect.resolve !== undefined;
}
function hasRejectAction(effect) {
return effect.reject !== undefined;
}
function hasUnsubscribeAction(effect) {
return effect.unsubscribe !== undefined;
}
function isObservableEffect(effect, operand) {
return isObservable(operand);
}
function isPromiseEffect(effect, operand) {
return operand instanceof Promise;
}
function isUnsubscriptionEffect(effect, operand) {
return operand.__brand === 'Unsubscribe';
}
function handleEffects(injector, runtime) {
return (reduced) => {
let newState = handleSliceEffects(reduced);
// tslint:disable-next-line:forin
for (const key in newState) {
newState = Object.assign(Object.assign({}, newState), { [key]: handleSliceEffects(newState[key]) });
}
return newState;
};
function handleSliceEffects(slicedState) {
if (isStateWithEffects(slicedState)) {
slicedState.effects.forEach((effect) => handleStateWithEffect(effect, runtime, injector.get(Store), injector));
return addEffectDescriptions(slicedState.state, slicedState.effects);
}
else {
return slicedState;
}
}
}
function isStateWithEffects(state) {
return (state === null || state === void 0 ? void 0 : state.__brand) === 'StateWithEffects';
}
function handleStateWithEffect(effect, runtime, store, injector) {
const operand = effect.operation(injector.get.bind(injector));
if (isObservableEffect(effect, operand)) {
const token = (Math.max(...runtime.keys()) + 1);
const subscription = operand.subscribe({
next: (value) => effect.next && store.dispatch(effect.next(value)),
error: (err) => effect.error && store.dispatch(effect.error(err)),
complete: () => effect.complete && store.dispatch(effect.complete())
});
runtime.set(token, subscription);
if (effect.subscribe) {
store.dispatch(effect.subscribe(token));
}
}
else if (isPromiseEffect(effect, operand)) {
operand.then((value) => effect.resolve && store.dispatch(effect.resolve(value)), (err) => effect.reject && store.dispatch(effect.reject(err)));
}
else if (isUnsubscriptionEffect(effect, operand)) {
handleUnsubscribe(operand, effect, runtime, store);
}
}
function handleUnsubscribe(operation, effect, runtime, store) {
const cancellable = runtime.get(operation.subscriptionToken);
if (cancellable instanceof Subscription) {
runtime.delete(operation.subscriptionToken);
cancellable.unsubscribe();
}
else if (cancellable instanceof AbortController) {
runtime.delete(operation.subscriptionToken);
cancellable.abort();
}
else {
console.warn(`SubscriptionToken ${operation.subscriptionToken} not recognized. Did you cancel this already?`);
}
if (effect.unsubscribe) {
store.dispatch(effect.unsubscribe(0));
}
}
function createRuntimeReducerFactory(reducerFactory, injector, metaReducers) {
const runtime = new Map();
// TODO: Set default counter value
runtime.set(31415, new Subscription());
if (Array.isArray(metaReducers) && metaReducers.length > 0) {
reducerFactory = compose.apply(null, [...metaReducers, reducerFactory]);
}
return (reducers, initialState) => {
const reducer = reducerFactory(reducers);
return (state, action) => {
state = state === undefined ? initialState : state;
return handleEffects(injector, runtime)(reducer(state, action));
};
};
}
const _REDUCER_FACTORY = new InjectionToken('ngrx-reducer-effects Internal Reducer Factory Provider');
const _RESOLVED_META_REDUCERS = new InjectionToken('ngrx-reducer-effects Internal Resolved Meta Reducers');
class EffectStoreModule extends StoreModule {
static forRoot(reducers, config = {}) {
var _a;
const store = StoreModule.forRoot(reducers, Object.assign(Object.assign({}, config), { runtimeChecks: Object.assign(Object.assign({}, config.runtimeChecks), { strictStateImmutability: false }) }));
return {
ngModule: store.ngModule,
providers: [
...((_a = store.providers) !== null && _a !== void 0 ? _a : []),
{
provide: _RESOLVED_META_REDUCERS,
deps: [META_REDUCERS, USER_PROVIDED_META_REDUCERS],
useFactory: _concatMetaReducers
},
{
provide: _REDUCER_FACTORY,
useValue: config.reducerFactory ? config.reducerFactory : combineReducers
},
{
provide: REDUCER_FACTORY,
deps: [_REDUCER_FACTORY, Injector, _RESOLVED_META_REDUCERS],
useFactory: createRuntimeReducerFactory
}
]
};
}
}
EffectStoreModule.decorators = [
{ type: NgModule, args: [{},] }
];
function _concatMetaReducers(metaReducers, userProvidedMetaReducers) {
return metaReducers.concat(userProvidedMetaReducers);
}
/*
* Public API Surface of ngrx-reducer-effects
*/
/**
* Generated bundle index. Do not edit.
*/
export { EffectStoreModule, createReducerEffect, unsubscribe, withEffects, unsubscribeBrand as ɵa, stateWithEffectsBrand as ɵb };
//# sourceMappingURL=ngrx-reducer-effects.js.map