@angular-redux/store
Version:
Angular bindings for Redux
734 lines (716 loc) • 24.7 kB
JavaScript
import { applyMiddleware, compose, createStore } from 'redux';
import { ApplicationRef, Injectable, NgZone, NgModule } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { __extends, __spread, __assign, __read } from 'tslib';
import { distinctUntilChanged, map, filter, switchMap } from 'rxjs/operators';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* This is the public interface of \@angular-redux/store. It wraps the global
* redux store and adds a few other add on methods. It's what you'll inject
* into your Angular application as a service.
* @abstract
* @template RootState
*/
var NgRedux = /** @class */ (function () {
function NgRedux() {
}
/**
* \@hidden, \@deprecated
*/
NgRedux.instance = undefined;
return NgRedux;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
var environment = (/** @type {?} */ ((typeof window !== 'undefined'
? window
: {})));
/**
* An angular-2-ified version of the Redux DevTools chrome extension.
*/
var DevToolsExtension = /** @class */ (function () {
/** @hidden */
function DevToolsExtension(appRef, ngRedux) {
var _this = this;
this.appRef = appRef;
this.ngRedux = ngRedux;
/**
* A wrapper for the Chrome Extension Redux DevTools.
* Makes sure state changes triggered by the extension
* trigger Angular2's change detector.
*
* @argument options: dev tool options; same
* format as described here:
* [zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md]
*/
this.enhancer = function (options) {
/** @type {?} */
var subscription;
if (!_this.isEnabled()) {
return null;
}
// Make sure changes from dev tools update angular's view.
(/** @type {?} */ (_this.getDevTools())).listen(function (_a) {
var type = _a.type;
if (type === 'START') {
subscription = _this.ngRedux.subscribe(function () {
if (!NgZone.isInAngularZone()) {
_this.appRef.tick();
}
});
}
else if (type === 'STOP') {
subscription();
}
});
return (/** @type {?} */ (_this.getDevTools()))(options || {});
};
/**
* Returns true if the extension is installed and enabled.
*/
this.isEnabled = function () { return !!_this.getDevTools(); };
/**
* Returns the redux devtools enhancer.
*/
this.getDevTools = function () {
return environment &&
(environment.__REDUX_DEVTOOLS_EXTENSION__ || environment.devToolsExtension);
};
}
DevToolsExtension.decorators = [
{ type: Injectable }
];
/** @nocollapse */
DevToolsExtension.ctorParameters = function () { return [
{ type: ApplicationRef },
{ type: NgRedux }
]; };
return DevToolsExtension;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Gets a deeply-nested property value from an object, given a 'path'
* of property names or array indices.
*
* @hidden
* @param {?} v
* @param {?} pathElems
* @return {?}
*/
function getIn(v, pathElems) {
if (!v) {
return v;
}
// If this is an ImmutableJS structure, use existing getIn function
if ('function' === typeof v.getIn) {
return v.getIn(pathElems);
}
var _a = __read(pathElems), firstElem = _a[0], restElems = _a.slice(1);
if (undefined === v[firstElem]) {
return undefined;
}
if (restElems.length === 0) {
return v[firstElem];
}
return getIn(v[firstElem], restElems);
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Sets a deeply-nested property value from an object, given a 'path'
* of property names or array indices. Path elements are created if
* not there already. Does not mutate the given object.
*
* @hidden
* @type {?}
*/
var setIn = function (obj, _a, value) {
var _b = __read(_a), firstElem = _b[0], restElems = _b.slice(1);
var _c, _d;
return 'function' === typeof (obj[firstElem] || {}).setIn
? __assign({}, obj, (_c = {}, _c[firstElem] = obj[firstElem].setIn(restElems, value), _c)) : __assign({}, obj, (_d = {}, _d[firstElem] = restElems.length === 0
? value
: setIn(obj[firstElem] || {}, restElems, value), _d));
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
var reducerMap = {};
/** @type {?} */
var composeReducers = function () {
var reducers = [];
for (var _i = 0; _i < arguments.length; _i++) {
reducers[_i] = arguments[_i];
}
return function (state, action) {
return reducers.reduce(function (subState, reducer) { return reducer(subState, action); }, state);
};
};
/**
* @param {?} rootReducer Call this on your root reducer to enable SubStore
* functionality for pre-configured stores (e.g. using NgRedux.provideStore()).
* NgRedux.configureStore
* does it for you under the hood.
* @return {?}
*/
function enableFractalReducers(rootReducer) {
reducerMap = {};
return composeReducers(rootFractalReducer, rootReducer);
}
/**
* @hidden
* @param {?} basePath
* @param {?} localReducer
* @return {?}
*/
function registerFractalReducer(basePath, localReducer) {
/** @type {?} */
var existingFractalReducer = reducerMap[JSON.stringify(basePath)];
if (existingFractalReducer && existingFractalReducer !== localReducer) {
throw new Error("attempt to overwrite fractal reducer for basePath " + basePath);
}
reducerMap[JSON.stringify(basePath)] = localReducer;
}
/**
* @hidden
* @param {?} basePath
* @param {?} nextLocalReducer
* @return {?}
*/
function replaceLocalReducer(basePath, nextLocalReducer) {
reducerMap[JSON.stringify(basePath)] = nextLocalReducer;
}
/**
* @param {?=} state
* @param {?=} action
* @return {?}
*/
function rootFractalReducer(state, action) {
if (state === void 0) { state = {}; }
/** @type {?} */
var fractalKey = action['@angular-redux::fractalkey'];
/** @type {?} */
var fractalPath = fractalKey ? JSON.parse(fractalKey) : [];
/** @type {?} */
var localReducer = reducerMap[fractalKey || ''];
return fractalKey && localReducer
? setIn(state, fractalPath, localReducer(getIn(state, fractalPath), action))
: state;
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* OPTIONS_KEY: this is per-class (static) and holds the config from the
* \@SubStore decorator.
* @type {?}
*/
var OPTIONS_KEY = '@angular-redux::substore::class::options';
/**
* INSTANCE_SUBSTORE_KEY, INSTANCE_SELECTIONS_KEY: these are per-instance
* (non-static) and holds references to the substores/selected observables
* to be used by an instance of a decorated class. I'm not using
* reflect-metadata here because I want
*
* 1. different instances to have different substores in the case where
* `basePathMethodName` is dynamic.
* 2. the instance substore to be garbage collected when the instance is no
* longer reachable.
* This is therefore an own-property on the actual instance of the decorated
* class.
* @type {?}
*/
var INSTANCE_SUBSTORE_KEY = '@angular-redux::substore::instance::store';
/** @type {?} */
var INSTANCE_SELECTIONS_KEY = '@angular-redux::substore::instance::selections';
/**
* Used to detect when the base path changes - this allows components to
* dynamically adjust their selections if necessary.
* @type {?}
*/
var INSTANCE_BASE_PATH_KEY = '@angular-redux::substore::instance::basepath';
/** @type {?} */
var getClassOptions = function (decoratedInstance) {
return decoratedInstance.constructor[OPTIONS_KEY];
};
/**
* @hidden
* @type {?}
*/
var setClassOptions = function (decoratedClassConstructor, options) {
decoratedClassConstructor[OPTIONS_KEY] = options;
};
// I want the store to be saved on the actual instance so
// 1. different instances can have distinct substores if necessary
// 2. the substore/selections will be marked for garbage collection when the
// instance is destroyed.
/** @type {?} */
var setInstanceStore = function (decoratedInstance, store) { return (decoratedInstance[INSTANCE_SUBSTORE_KEY] = store); };
/** @type {?} */
var getInstanceStore = function (decoratedInstance) {
return decoratedInstance[INSTANCE_SUBSTORE_KEY];
};
/** @type {?} */
var getInstanceSelectionMap = function (decoratedInstance) {
/** @type {?} */
var map$$1 = decoratedInstance[INSTANCE_SELECTIONS_KEY] || {};
decoratedInstance[INSTANCE_SELECTIONS_KEY] = map$$1;
return map$$1;
};
/** @type {?} */
var hasBasePathChanged = function (decoratedInstance, basePath) {
return decoratedInstance[INSTANCE_BASE_PATH_KEY] !== (basePath || []).toString();
};
/** @type {?} */
var setInstanceBasePath = function (decoratedInstance, basePath) {
decoratedInstance[INSTANCE_BASE_PATH_KEY] = (basePath || []).toString();
};
/** @type {?} */
var clearInstanceState = function (decoratedInstance) {
decoratedInstance[INSTANCE_SELECTIONS_KEY] = null;
decoratedInstance[INSTANCE_SUBSTORE_KEY] = null;
decoratedInstance[INSTANCE_BASE_PATH_KEY] = null;
};
/**
* Gets the store associated with a decorated instance (e.g. a
* component or service)
* @hidden
* @type {?}
*/
var getBaseStore = function (decoratedInstance) {
// The root store hasn't been set up yet.
if (!NgRedux.instance) {
return undefined;
}
/** @type {?} */
var options = getClassOptions(decoratedInstance);
// This is not decorated with `@WithSubStore`. Return the root store.
if (!options) {
return NgRedux.instance;
}
// Dynamic base path support:
/** @type {?} */
var basePath = decoratedInstance[options.basePathMethodName]();
if (hasBasePathChanged(decoratedInstance, basePath)) {
clearInstanceState(decoratedInstance);
setInstanceBasePath(decoratedInstance, basePath);
}
if (!basePath) {
return NgRedux.instance;
}
/** @type {?} */
var store = getInstanceStore(decoratedInstance);
if (!store) {
setInstanceStore(decoratedInstance, NgRedux.instance.configureSubStore(basePath, options.localReducer));
}
return getInstanceStore(decoratedInstance);
};
/**
* Creates an Observable from the given selection parameters,
* rooted at decoratedInstance's store, and caches it on the
* instance for future use.
* @hidden
* @type {?}
*/
var getInstanceSelection = function (decoratedInstance, key, selector, transformer, comparator) {
/** @type {?} */
var store = getBaseStore(decoratedInstance);
if (store) {
/** @type {?} */
var selections = getInstanceSelectionMap(decoratedInstance);
selections[key] =
selections[key] ||
(!transformer
? store.select(selector, comparator)
: store.select(selector).pipe(function (obs$) { return transformer(obs$, decoratedInstance); }, distinctUntilChanged(comparator)));
return selections[key];
}
return undefined;
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Auto-dispatches the return value of the decorated function.
*
* Decorate a function creator method with \@dispatch and its return
* value will automatically be passed to ngRedux.dispatch() for you.
* @return {?}
*/
function dispatch() {
return function decorate(target, key, descriptor) {
/** @type {?} */
var originalMethod;
/** @type {?} */
var wrapped = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
/** @type {?} */
var result = originalMethod.apply(this, args);
if (result !== undefined) {
/** @type {?} */
var store = getBaseStore(this) || NgRedux.instance;
if (store) {
store.dispatch(result);
}
}
return result;
};
descriptor = descriptor || Object.getOwnPropertyDescriptor(target, key);
if (descriptor === undefined) {
/** @type {?} */
var dispatchDescriptor = {
get: function () { return wrapped; },
set: function (setMethod) { return (originalMethod = setMethod); },
};
Object.defineProperty(target, key, dispatchDescriptor);
return dispatchDescriptor;
}
else {
originalMethod = descriptor.value;
descriptor.value = wrapped;
return descriptor;
}
};
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Selects an observable from the store, and attaches it to the decorated
* property.
*
* ```ts
* import { select } from '\@angular-redux/store';
*
* class SomeClass {
* \@select(['foo','bar']) foo$: Observable<string>
* }
* ```
*
* @template T
* @param {?=} selector
* A selector function, property name string, or property name path
* (array of strings/array indices) that locates the store data to be
* selected
*
* @param {?=} comparator Function used to determine if this selector has changed.
* @return {?}
*/
function select(selector, comparator) {
return function (target, key) {
/** @type {?} */
var adjustedSelector = selector
? selector
: String(key).lastIndexOf('$') === String(key).length - 1
? String(key).substring(0, String(key).length - 1)
: key;
decorate(adjustedSelector, undefined, comparator)(target, key);
};
}
/**
* Selects an observable using the given path selector, and runs it through the
* given transformer function. A transformer function takes the store
* observable as an input and returns a derived observable from it. That derived
* observable is run through distinctUntilChanges with the given optional
* comparator and attached to the store property.
*
* Think of a Transformer as a FunctionSelector that operates on observables
* instead of values.
*
* ```ts
* import { select$ } from 'angular-redux/store';
*
* export const debounceAndTriple = obs$ => obs$
* .debounce(300)
* .map(x => 3 * x);
*
* class Foo {
* \@select$(['foo', 'bar'], debounceAndTriple)
* readonly debouncedFooBar$: Observable<number>;
* }
* ```
* @template T
* @param {?} selector
* @param {?} transformer
* @param {?=} comparator
* @return {?}
*/
function select$(selector, transformer, comparator) {
return decorate(selector, transformer, comparator);
}
/**
* @param {?} selector
* @param {?=} transformer
* @param {?=} comparator
* @return {?}
*/
function decorate(selector, transformer, comparator) {
return function decorator(target, key) {
/**
* @this {?}
* @return {?}
*/
function getter() {
return getInstanceSelection(this, key, selector, transformer, comparator);
}
// Replace decorated property with a getter that returns the observable.
if (delete target[key]) {
Object.defineProperty(target, key, {
get: getter,
enumerable: true,
configurable: true,
});
}
};
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Modifies the behaviour of any `\@select`, `\@select$`, or `\@dispatch`
* decorators to operate on a substore defined by the IFractalStoreOptions.
*
* See:
* https://github.com/angular-redux/platform/blob/master/packages/store/articles/fractal-store.md
* for more information about SubStores.
* @param {?} __0
* @return {?}
*/
function WithSubStore(_a) {
var basePathMethodName = _a.basePathMethodName, localReducer = _a.localReducer;
return function decorate(constructor) {
setClassOptions(constructor, {
basePathMethodName: basePathMethodName,
localReducer: localReducer,
});
};
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @hidden
* @type {?}
*/
var assert = function (condition, message) {
if (!condition) {
throw new Error(message);
}
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @hidden
* @type {?}
*/
var sniffSelectorType = function (selector) {
return !selector
? 'nil'
: Array.isArray(selector)
? 'path'
: 'function' === typeof selector
? 'function'
: 'property';
};
/**
* @hidden
* @type {?}
*/
var resolver = function (selector) { return ({
property: function (state) {
return state ? state[(/** @type {?} */ (selector))] : undefined;
},
path: function (state) { return getIn(state, (/** @type {?} */ (selector))); },
function: (/** @type {?} */ (selector)),
nil: function (state) { return state; },
}); };
/**
* @hidden
* @type {?}
*/
var resolveToFunctionSelector = function (selector) { return resolver(selector)[sniffSelectorType(selector)]; };
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @hidden
* @template State
*/
var /**
* @hidden
* @template State
*/
SubStore = /** @class */ (function () {
function SubStore(rootStore, basePath, localReducer) {
var _this = this;
this.rootStore = rootStore;
this.basePath = basePath;
this.dispatch = function (action) {
return _this.rootStore.dispatch(__assign({}, ((/** @type {?} */ (action))), { '@angular-redux::fractalkey': JSON.stringify(_this.basePath) }));
};
this.getState = function () { return getIn(_this.rootStore.getState(), _this.basePath); };
this.configureSubStore = function (basePath, localReducer) {
return new SubStore(_this.rootStore, __spread(_this.basePath, basePath), localReducer);
};
this.select = function (selector, comparator) {
return _this.rootStore.select(_this.basePath).pipe(map(resolveToFunctionSelector(selector)), distinctUntilChanged(comparator));
};
this.subscribe = function (listener) {
/** @type {?} */
var subscription = _this.select().subscribe(listener);
return function () { return subscription.unsubscribe(); };
};
this.replaceReducer = function (nextLocalReducer) {
return replaceLocalReducer(_this.basePath, nextLocalReducer);
};
registerFractalReducer(basePath, localReducer);
}
return SubStore;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @hidden
* @template RootState
*/
var /**
* @hidden
* @template RootState
*/
RootStore = /** @class */ (function (_super) {
__extends(RootStore, _super);
function RootStore(ngZone) {
var _this = _super.call(this) || this;
_this.ngZone = ngZone;
_this.store = undefined;
_this.configureStore = function (rootReducer, initState, middleware, enhancers) {
if (middleware === void 0) { middleware = []; }
if (enhancers === void 0) { enhancers = []; }
assert(!_this.store, 'Store already configured!');
// Variable-arity compose in typescript FTW.
_this.setStore(compose.apply(void 0, __spread([applyMiddleware.apply(void 0, __spread(middleware))], enhancers))(createStore)(enableFractalReducers(rootReducer), initState));
};
_this.provideStore = function (store) {
assert(!_this.store, 'Store already configured!');
_this.setStore(store);
};
_this.getState = function () { return (/** @type {?} */ (_this.store)).getState(); };
_this.subscribe = function (listener) {
return (/** @type {?} */ (_this.store)).subscribe(listener);
};
_this.replaceReducer = function (nextReducer) {
(/** @type {?} */ (_this.store)).replaceReducer(nextReducer);
};
_this.dispatch = function (action) {
assert(!!_this.store, 'Dispatch failed: did you forget to configure your store? ' +
'https://github.com/angular-redux/platform/blob/master/packages/store/' +
'README.md#quick-start');
if (!NgZone.isInAngularZone()) {
return _this.ngZone.run(function () { return (/** @type {?} */ (_this.store)).dispatch(action); });
}
else {
return (/** @type {?} */ (_this.store)).dispatch(action);
}
};
_this.select = function (selector, comparator) {
return _this.store$.pipe(distinctUntilChanged(), map(resolveToFunctionSelector(selector)), distinctUntilChanged(comparator));
};
_this.configureSubStore = function (basePath, localReducer) {
return new SubStore(_this, basePath, localReducer);
};
_this.storeToObservable = function (store) {
return new Observable(function (observer) {
observer.next(store.getState());
/** @type {?} */
var unsubscribeFromRedux = store.subscribe(function () {
return observer.next(store.getState());
});
return function () {
unsubscribeFromRedux();
observer.complete();
};
});
};
NgRedux.instance = _this;
_this.store$ = (/** @type {?} */ (new BehaviorSubject(undefined).pipe(filter(function (n) { return n !== undefined; }), switchMap(function (observableStore) { return (/** @type {?} */ (observableStore)); }))));
return _this;
}
/**
* @private
* @param {?} store
* @return {?}
*/
RootStore.prototype.setStore = /**
* @private
* @param {?} store
* @return {?}
*/
function (store) {
this.store = store;
/** @type {?} */
var storeServable = this.storeToObservable(store);
this.store$.next((/** @type {?} */ (storeServable)));
};
return RootStore;
}(NgRedux));
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @hidden
* @param {?} ngZone
* @return {?}
*/
function _ngReduxFactory(ngZone) {
return new RootStore(ngZone);
}
var NgReduxModule = /** @class */ (function () {
function NgReduxModule() {
}
NgReduxModule.decorators = [
{ type: NgModule, args: [{
providers: [
DevToolsExtension,
{ provide: NgRedux, useFactory: _ngReduxFactory, deps: [NgZone] },
],
},] }
];
return NgReduxModule;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
export { NgRedux, NgReduxModule, DevToolsExtension, enableFractalReducers, select, select$, dispatch, WithSubStore, RootStore as ɵb, _ngReduxFactory as ɵa };
//# sourceMappingURL=angular-redux-store.js.map