UNPKG

ngrx-reducer-effects

Version:

Return side-effects as data from your NgRx reducers

205 lines (193 loc) 7.79 kB
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