UNPKG

@ngxs/store

Version:
351 lines (336 loc) 14 kB
import * as i0 from '@angular/core'; import { InjectionToken, inject, DestroyRef, Injectable, untracked } from '@angular/core'; import { BehaviorSubject, Subject, Observable } from 'rxjs'; import { toSignal } from '@angular/core/rxjs-interop'; // This key is used to store metadata on state classes, // such as actions and other related information. const ɵMETA_KEY = 'NGXS_META'; // This key is used to store options on state classes // provided through the `@State` decorator. const ɵMETA_OPTIONS_KEY = 'NGXS_OPTIONS_META'; // This key is used to store selector metadata on selector functions, // such as decorated with the `@Selector` or provided through the // `createSelector` function. const ɵSELECTOR_META_KEY = 'NGXS_SELECTOR_META'; // Property reads are not minified. // It's smaller to read it once and use a function. const _hasOwnProperty = Object.prototype.hasOwnProperty; const ɵhasOwnProperty = (target, key) => _hasOwnProperty.call(target, key); const ɵdefineProperty = Object.defineProperty; /** * Ensures metadata is attached to the class and returns it. * * @ignore */ function ɵensureStoreMetadata(target) { if (!ɵhasOwnProperty(target, ɵMETA_KEY)) { const defaultMetadata = { name: null, actions: {}, defaults: {}, path: null, makeRootSelector(context) { return context.getStateGetter(defaultMetadata.name); }, children: [] }; ɵdefineProperty(target, ɵMETA_KEY, { value: defaultMetadata }); } return ɵgetStoreMetadata(target); } /** * Get the metadata attached to the state class if it exists. * * @ignore */ function ɵgetStoreMetadata(target) { return target[ɵMETA_KEY]; } /** * Ensures metadata is attached to the selector and returns it. * * @ignore */ function ɵensureSelectorMetadata(target) { if (!ɵhasOwnProperty(target, ɵSELECTOR_META_KEY)) { const defaultMetadata = { makeRootSelector: null, originalFn: null, containerClass: null, selectorName: null, getSelectorOptions: () => ({}) }; ɵdefineProperty(target, ɵSELECTOR_META_KEY, { value: defaultMetadata }); } return ɵgetSelectorMetadata(target); } /** * Get the metadata attached to the selector if it exists. * * @ignore */ function ɵgetSelectorMetadata(target) { return target[ɵSELECTOR_META_KEY]; } function areArgumentsShallowlyEqual(equalityCheck, prev, next) { if (prev === null || next === null || prev.length !== next.length) { return false; } // Do this in a for loop (and not a `forEach` or an `every`) so we can // determine equality as fast as possible. const length = prev.length; for (let i = 0; i < length; i++) { if (!equalityCheck(prev[i], next[i])) { return false; } } return true; } /** * Memoize a function on its last inputs only. * Originally from: https://github.com/reduxjs/reselect/blob/master/src/index.js * * @ignore */ function ɵmemoize(func, equalityCheck = Object.is) { let lastArgs = null; let lastResult = null; // we reference arguments instead of spreading them for performance reasons function memoized() { // eslint-disable-next-line prefer-rest-params if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) { // apply arguments instead of spreading for performance. // eslint-disable-next-line prefer-rest-params, prefer-spread lastResult = func.apply(null, arguments); } // eslint-disable-next-line prefer-rest-params lastArgs = arguments; return lastResult; } memoized.reset = function () { // The hidden (for now) ability to reset the memoization lastArgs = null; lastResult = null; }; return memoized; } class StateToken { _name; constructor(_name) { this._name = _name; const selectorMetadata = ɵensureSelectorMetadata(this); selectorMetadata.makeRootSelector = (runtimeContext) => { return runtimeContext.getStateGetter(this._name); }; } getName() { return this._name; } toString() { return `StateToken[${this._name}]`; } } class ɵInitialState { static _value = {}; static set(state) { this._value = state; } static pop() { const state = this._value; this._value = {}; return state; } } const ɵINITIAL_STATE_TOKEN = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'INITIAL_STATE_TOKEN' : '', { providedIn: 'root', factory: () => ɵInitialState.pop() }); class ɵNgxsAppBootstrappedState extends BehaviorSubject { constructor() { super(false); const destroyRef = inject(DestroyRef); // Complete the subject once the root injector is destroyed to ensure // there are no active subscribers that would receive events or perform // any actions after the application is destroyed. destroyRef.onDestroy(() => this.complete()); } bootstrap() { this.next(true); } /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsAppBootstrappedState, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsAppBootstrappedState, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsAppBootstrappedState, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); // These tokens are internal and can change at any point. const ɵNGXS_STATE_FACTORY = /* @__PURE__ */ new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'ɵNGXS_STATE_FACTORY' : ''); const ɵNGXS_STATE_CONTEXT_FACTORY = /* @__PURE__ */ new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'ɵNGXS_STATE_CONTEXT_FACTORY' : ''); /** * This wraps the provided function, and will enforce the following: * - The calls will execute in the order that they are made * - A call will only be initiated when the previous call has completed * - If there is a call currently executing then the new call will be added * to the queue and the function will return immediately * * NOTE: The following assumptions about the operation must hold true: * - The operation is synchronous in nature * - If any asynchronous side effects of the call exist, it should not * have any bearing on the correctness of the next call in the queue * - The operation has a void return * - The caller should not assume that the call has completed upon * return of the function * - The caller can assume that all the queued calls will complete * within the current microtask * - The only way that a call will encounter another call in the queue * would be if the call at the front of the queue initiated this call * as part of its synchronous execution */ function orderedQueueOperation(operation) { const callsQueue = []; let busyPushingNext = false; return function callOperation(...args) { if (busyPushingNext) { callsQueue.unshift(args); return; } busyPushingNext = true; operation(...args); while (callsQueue.length > 0) { const nextCallArgs = callsQueue.pop(); nextCallArgs && operation(...nextCallArgs); } busyPushingNext = false; }; } /** * Custom Subject that ensures that subscribers are notified of values in the order that they arrived. * A standard Subject does not have this guarantee. * For example, given the following code: * ```typescript * const subject = new Subject<string>(); subject.subscribe(value => { if (value === 'start') subject.next('end'); }); subject.subscribe(value => { }); subject.next('start'); * ``` * When `subject` is a standard `Subject<T>` the second subscriber would recieve `end` and then `start`. * When `subject` is a `OrderedSubject<T>` the second subscriber would recieve `start` and then `end`. */ class ɵOrderedSubject extends Subject { _orderedNext = orderedQueueOperation((value) => super.next(value)); next(value) { this._orderedNext(value); } } /** * Custom BehaviorSubject that ensures that subscribers are notified of values in the order that they arrived. * A standard BehaviorSubject does not have this guarantee. * For example, given the following code: * ```typescript * const subject = new BehaviorSubject<string>(); subject.subscribe(value => { if (value === 'start') subject.next('end'); }); subject.subscribe(value => { }); subject.next('start'); * ``` * When `subject` is a standard `BehaviorSubject<T>` the second subscriber would recieve `end` and then `start`. * When `subject` is a `OrderedBehaviorSubject<T>` the second subscriber would recieve `start` and then `end`. */ class ɵOrderedBehaviorSubject extends BehaviorSubject { _orderedNext = orderedQueueOperation((value) => super.next(value)); _currentValue; constructor(value) { super(value); this._currentValue = value; } getValue() { return this._currentValue; } next(value) { this._currentValue = value; this._orderedNext(value); } } function ɵwrapObserverCalls(invokeFn) { return (source) => { return new Observable(subscriber => { return source.subscribe({ next(value) { invokeFn(() => subscriber.next(value)); }, error(error) { invokeFn(() => subscriber.error(error)); }, complete() { invokeFn(() => subscriber.complete()); } }); }); }; } /** * BehaviorSubject of the entire state. * @ignore */ class ɵStateStream extends ɵOrderedBehaviorSubject { state = toSignal(this.pipewrapObserverCalls(untracked)), { manualCleanup: true, requireSync: true }); constructor() { super({}); // Complete the subject once the root injector is destroyed to ensure // there are no active subscribers that would receive events or perform // any actions after the application is destroyed. // The `StateStream` should never emit values once the root view is removed, // such as when the `ApplicationRef.destroy()` method is called. This is crucial // for preventing memory leaks in server-side rendered apps, where a new `StateStream` // is created for each HTTP request. If users forget to unsubscribe from `store.select` // or `store.subscribe`, it can result in significant memory leaks in SSR apps. inject(DestroyRef).onDestroy(() => this.complete()); } /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵStateStream, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵStateStream, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵStateStream, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); class ɵNgxsActionRegistry { // Instead of going over the states list every time an action is dispatched, // we are constructing a map of action types to lists of action metadata. // If the `@@Init` action is handled in two different states, the action // metadata list will contain two objects that have the state `instance` and // method names to be used as action handlers (decorated with `@Action(InitState)`). _actionTypeToHandlersMap = new Map(); constructor() { inject(DestroyRef).onDestroy(() => this._actionTypeToHandlersMap.clear()); } get(type) { return this._actionTypeToHandlersMap.get(type); } register(type, handler) { const handlers = this._actionTypeToHandlersMap.get(type) ?? new Set(); handlers.add(handler); this._actionTypeToHandlersMap.set(type, handlers); return () => { const handlers = this._actionTypeToHandlersMap.get(type); handlers.delete(handler); }; } /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsActionRegistry, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ɵNgxsActionRegistry, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); /** * Generated bundle index. Do not edit. */ export { StateToken, ɵINITIAL_STATE_TOKEN, ɵInitialState, ɵMETA_KEY, ɵMETA_OPTIONS_KEY, ɵNGXS_STATE_CONTEXT_FACTORY, ɵNGXS_STATE_FACTORY, ɵNgxsActionRegistry, ɵNgxsAppBootstrappedState, ɵOrderedBehaviorSubject, ɵOrderedSubject, ɵSELECTOR_META_KEY, ɵStateStream, ɵdefineProperty, ɵensureSelectorMetadata, ɵensureStoreMetadata, ɵgetSelectorMetadata, ɵgetStoreMetadata, ɵhasOwnProperty, ɵmemoize, ɵwrapObserverCalls }; //# sourceMappingURL=ngxs-store-internals.mjs.map