UNPKG

ngrx-run

Version:

Return side-effects as data from your NgRx reducers

283 lines (270 loc) 9.88 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 inject(token) { return token; } /* General */ function createEffect(type, config) { // tslint:disable-next-line:only-arrow-functions typedef return function (params) { const hasParams = arguments.length > 0; const hasDependencies = config.using; if (hasParams && hasDependencies) { return { __isEffect: true, type, call: (...deps) => config.call(...deps)(params), using: config.using, params }; } else if (!hasParams) { return { __isEffect: true, type, call: (...deps) => config.call(...deps), using: config.using, params }; } else { return { __isEffect: true, type, call: () => config.call(params), using: config.using, params }; } }; } const unsubscribeBrand = 'Unsubscribe'; function unsubscribe(subscriptionToken, type) { return { __isEffect: true, type: type !== null && type !== void 0 ? type : 'Unsubscribe', call: () => ({ __brand: unsubscribeBrand, subscriptionToken }) }; } function run(effect, handler) { return Object.assign(Object.assign({}, effect), handler); } 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.subscribed(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.subscribed !== undefined; } function hasDependencies(effectLike) { return effectLike.using != null; } function isObservableEffect(effect, operand) { return isObservable(operand); } function isObservableOperand(operand) { return isObservable(operand); } function isPromiseEffect(effect, operand) { return operand instanceof Promise; } function isPromiseOperand(operand) { return operand instanceof Promise; } function isUnsubscriptionEffect(effect, operand) { var _a; return ((_a = operand) === null || _a === void 0 ? void 0 : _a.__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)) { const [state, ...effects] = slicedState; effects.map(effect => handleStateWithEffect(effect, runtime, injector.get(Store), injector)); return addEffectDescriptions(state, effects); } else { return slicedState; } } } function isStateWithEffects(state) { return state && state[1] && state[1].__isEffect === true; } function handleStateWithEffect(effect, runtime, store, injector) { let operand; const deps = hasDependencies(effect) ? effect.using.map((dep) => injector.get(dep)) : []; try { // @ts-ignore operand = effect.call(...deps); } catch (err) { if (effect.error) { try { store.dispatch(effect.error(err)); } catch (innerError) { console.error(`Unhandled error occurred when creating error action for effect "${effect.type}"`, innerError); } } else { console.error(`Unhandled error occurred when creating operation for effect "${effect.type}"`, err); } } if (isObservableEffect(effect, operand) && isObservableOperand(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.subscribed) { store.dispatch(effect.subscribed(token)); } } else if (isPromiseEffect(effect, operand) && isPromiseOperand(operand)) { operand.then((value) => effect.complete && store.dispatch(effect.complete(value)), (err) => effect.error && store.dispatch(effect.error(err))); } else if (isUnsubscriptionEffect(effect, operand)) { handleUnsubscribe(operand, effect, runtime, store); } else { const complete = effect.complete; if (complete) { store.dispatch(complete()); } } } 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.complete) { store.dispatch(effect.complete()); } } 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-run Internal Reducer Factory Provider'); const _RESOLVED_META_REDUCERS = new InjectionToken('ngrx-run 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); } function isActionOf(action, actions) { return Object.values(actions) .map((a) => a.type) .includes(action.type); } function childReducer(state, key, action, reducer) { return Object.assign(Object.assign({}, state), { [key]: reducer(state[key], action) }); } function tokenized(actionCreator, token) { return ((p) => Object.assign(actionCreator(p), { token })); } function hasToken(maybeTokenized, token) { return isTokenized(maybeTokenized) && maybeTokenized.token === token; } function createToken(token) { return token; } function isTokenized(maybeTokenized) { return maybeTokenized.token != null; } /* * Public API Surface of ngrx-run */ /** * Generated bundle index. Do not edit. */ export { EffectStoreModule, childReducer, createEffect, createToken, hasToken, isActionOf, run, tokenized, unsubscribe }; //# sourceMappingURL=ngrx-run.js.map