UNPKG

@lithiumjs/angular

Version:

Reactive components made easy. Lithium provides utilities that enable seamless reactive state and event interactions for Angular components.

1,045 lines (1,033 loc) 80.6 kB
import { Subject, Subscription, EMPTY, Observable, BehaviorSubject, ReplaySubject, from, throwError, merge, combineLatest, forkJoin, of } from 'rxjs'; import { EventEmitter, resolveForwardRef, Injector, InjectionToken } from '@angular/core'; import { mergeMap, skip, map, tap, distinctUntilChanged, filter, switchMap, takeUntil, flatMap, take } from 'rxjs/operators'; var Metadata; (function (Metadata) { const LI_METADATA_ROOT = Symbol('$LI_'); function requireMetadata(symbol, target, defaultValue) { if (!hasMetadata(symbol, target)) { setMetadata(symbol, target, defaultValue); } return getOwnMetadata(symbol, target) || getMetadata(symbol, target); } Metadata.requireMetadata = requireMetadata; function requireOwnMetadata(symbol, target, defaultValue) { if (!hasOwnMetadata(symbol, target)) { setMetadata(symbol, target, defaultValue); } return getOwnMetadata(symbol, target); } Metadata.requireOwnMetadata = requireOwnMetadata; function getMetadataMap(target) { return (getMetadataKeys(target) || []) .reduce((map, key) => map.set(key, getMetadata(key, target)), new Map()); } Metadata.getMetadataMap = getMetadataMap; function setMetadata(symbol, target, value) { Object.defineProperty(rootMetadata(target), symbol, { writable: true, enumerable: true, value }); } Metadata.setMetadata = setMetadata; function hasMetadata(symbol, target) { return !!getMetadata(symbol, target); // TODO } Metadata.hasMetadata = hasMetadata; function hasOwnMetadata(symbol, target) { return !!getOwnMetadata(symbol, target); } Metadata.hasOwnMetadata = hasOwnMetadata; function getMetadata(symbol, target) { const metadata = getOwnMetadata(symbol, target); if (!metadata && target.prototype) { return getMetadata(symbol, target.prototype); } return metadata; } Metadata.getMetadata = getMetadata; function getOwnMetadata(symbol, target) { const descriptor = Object.getOwnPropertyDescriptor(rootMetadata(target), symbol); return descriptor ? descriptor.value : undefined; } Metadata.getOwnMetadata = getOwnMetadata; function getMetadataKeys(target) { return Object.keys(rootMetadata(target)); } Metadata.getMetadataKeys = getMetadataKeys; function ensureRootMetadataExists(target) { if (!Object.getOwnPropertyDescriptor(target, LI_METADATA_ROOT)) { Object.defineProperty(target, LI_METADATA_ROOT, { enumerable: false, writable: true, value: Object.create({}) }); } } function rootMetadata(target) { ensureRootMetadataExists(target); return Object.getOwnPropertyDescriptor(target, LI_METADATA_ROOT).value; } })(Metadata || (Metadata = {})); var ComponentStateMetadata; (function (ComponentStateMetadata) { ComponentStateMetadata.MANAGED_PROPERTY_LIST_KEY = "MANAGED_PROPERTY_LIST_KEY"; const ManagedPropertyListSymbol = Symbol("ManagedPropertyList"); function GetOwnManagedPropertyList(target) { return Metadata.requireOwnMetadata(ManagedPropertyListSymbol, target, []); } ComponentStateMetadata.GetOwnManagedPropertyList = GetOwnManagedPropertyList; function GetInheritedManagedPropertyList(target) { const targetMetadata = GetOwnManagedPropertyList(target).slice(0); const targetPrototype = Object.getPrototypeOf(target); if (targetPrototype) { targetMetadata.push(...GetInheritedManagedPropertyList(targetPrototype)); } return targetMetadata; } ComponentStateMetadata.GetInheritedManagedPropertyList = GetInheritedManagedPropertyList; function SetManagedPropertyList(target, list) { Metadata.setMetadata(ManagedPropertyListSymbol, target, list); } ComponentStateMetadata.SetManagedPropertyList = SetManagedPropertyList; function AddManagedProperty(target, property) { SetManagedPropertyList(target, GetOwnManagedPropertyList(target).concat([property])); } ComponentStateMetadata.AddManagedProperty = AddManagedProperty; })(ComponentStateMetadata || (ComponentStateMetadata = {})); function asyncStateKey(key) { return `${key}$`; } var CommonMetadata; (function (CommonMetadata) { CommonMetadata.MANAGED_ONDESTROY_KEY = "__LI__MANAGED__ONDESTROY__"; CommonMetadata.MANAGED_INSTANCE_DESTROYED_KEY = "__LI__MANAGED__INSTANCE__DESTROYED__"; function instanceIsDestroyed(componentInstance) { return !!Metadata.getOwnMetadata(CommonMetadata.MANAGED_INSTANCE_DESTROYED_KEY, componentInstance); } CommonMetadata.instanceIsDestroyed = instanceIsDestroyed; })(CommonMetadata || (CommonMetadata = {})); /** @deprecated */ var EmitterMetadata; (function (EmitterMetadata) { EmitterMetadata.BOOTSTRAPPED_KEY = "$$STATEEMITTER_BOOTSTRAPPED"; let ProxyMode; (function (ProxyMode) { ProxyMode.None = "None"; ProxyMode.From = "From"; ProxyMode.Alias = "Alias"; ProxyMode.Merge = "Merge"; })(ProxyMode = EmitterMetadata.ProxyMode || (EmitterMetadata.ProxyMode = {})); let SubjectInfo; (function (SubjectInfo) { function IsDynamicAlias(subjectInfo) { return !IsStaticAlias(subjectInfo); } SubjectInfo.IsDynamicAlias = IsDynamicAlias; function IsStaticAlias(subjectInfo) { return (subjectInfo.observable instanceof Subject); } SubjectInfo.IsStaticAlias = IsStaticAlias; function IsSelfProxy(subjectInfo) { return subjectInfo.proxyPath === subjectInfo.propertyKey; } SubjectInfo.IsSelfProxy = IsSelfProxy; })(SubjectInfo = EmitterMetadata.SubjectInfo || (EmitterMetadata.SubjectInfo = {})); EmitterMetadata.EmitterMapSymbol = Symbol("EmitterMapSymbol"); /** @description Gets the metadata map object for the given target class (or its inheritted classes). */ function GetMetadataMap(target) { return Metadata.requireMetadata(EmitterMetadata.EmitterMapSymbol, target, new Map()); } EmitterMetadata.GetMetadataMap = GetMetadataMap; /** @description Gets the metadata map object for the given target class. */ function GetOwnMetadataMap(target) { return Metadata.requireOwnMetadata(EmitterMetadata.EmitterMapSymbol, target, new Map()); } EmitterMetadata.GetOwnMetadataMap = GetOwnMetadataMap; function HasOwnMetadataMap(target) { return Metadata.hasOwnMetadata(EmitterMetadata.EmitterMapSymbol, target); } EmitterMetadata.HasOwnMetadataMap = HasOwnMetadataMap; function SetMetadataMap(target, map) { Metadata.setMetadata(EmitterMetadata.EmitterMapSymbol, target, map); } EmitterMetadata.SetMetadataMap = SetMetadataMap; /** @description Copy all metadata from the source map to the target map. * * Note: This mutates the target map. **/ function CopyMetadata(target, source, overwrite) { // Iterate over all source metadata properties... source.forEach((subjectInfo, eventType) => { // And add them to this class' metadata map if not already defined if (overwrite || !target.has(eventType)) { target.set(eventType, Object.assign({}, subjectInfo)); } }); return target; } EmitterMetadata.CopyMetadata = CopyMetadata; /** @description Merge own and inheritted metadata into a single map. * * Note: This mutates the object's metadata. **/ function CopyInherittedMetadata(object) { if (object) { let metadataMap = GetMetadataMap(object); let inherittedMap = CopyInherittedMetadata(Object.getPrototypeOf(object)); return CopyMetadata(metadataMap, inherittedMap); } return new Map(); } EmitterMetadata.CopyInherittedMetadata = CopyInherittedMetadata; })(EmitterMetadata || (EmitterMetadata = {})); var EventMetadata; (function (EventMetadata) { EventMetadata.SUBJECT_TABLE_MERGED_KEY = "$$EVENTSOURCE_SUBJECT_TABLE_MERGED"; EventMetadata.BOOTSTRAPPED_KEY = "$$EVENTSOURCE_BOOTSTRAPPED"; EventMetadata.LIFECYCLE_REGISTRATION_KEY = "$$EVENTSOURCE_LIFECYCLE_REGISTRATION"; const EventSubjectTableSymbol = Symbol("EventSubjectTableSymbol"); const InstanceBootstrapMapSymbol = Symbol("InstanceBootstrapMapSymbol"); const LifecycleRegistrationMapSymbol = Symbol("LifecycleRegistrationMapSymbol"); const LifecycleCallbackMapSymbol = Symbol("LifecycleCallbackMapSymbol"); /** @description Gets the metadata map object for the given target class (or its inheritted classes). */ function GetEventSubjectTable(target) { return Metadata.requireMetadata(EventSubjectTableSymbol, target, new Map()); } EventMetadata.GetEventSubjectTable = GetEventSubjectTable; /** @description Gets the metadata map object for the given target class. */ function GetOwnEventSubjectTable(target) { return Metadata.requireOwnMetadata(EventSubjectTableSymbol, target, new Map()); } EventMetadata.GetOwnEventSubjectTable = GetOwnEventSubjectTable; function GetInstanceBootstrapMap(target) { return Metadata.requireMetadata(InstanceBootstrapMapSymbol, target, new Map()); } EventMetadata.GetInstanceBootstrapMap = GetInstanceBootstrapMap; function GetOwnLifecycleRegistrationMap(target) { return Metadata.requireOwnMetadata(LifecycleRegistrationMapSymbol, target, new Map()); } EventMetadata.GetOwnLifecycleRegistrationMap = GetOwnLifecycleRegistrationMap; /** @description Gets own and inherited lifecycle registration data merged into a single Map. */ function GetLifecycleRegistrationMap(target) { const metadata = new Map(); const ownMetadata = GetOwnLifecycleRegistrationMap(target); ownMetadata.forEach((v, k) => metadata.set(k, v)); const targetPrototype = Object.getPrototypeOf(target); if (targetPrototype) { const inherittedMetadata = GetLifecycleRegistrationMap(targetPrototype); inherittedMetadata.forEach((v, k) => metadata.set(k, v ?? metadata.get(k))); } return metadata; } EventMetadata.GetLifecycleRegistrationMap = GetLifecycleRegistrationMap; function GetOwnLifecycleCallbackMap(target) { return Metadata.requireOwnMetadata(LifecycleCallbackMapSymbol, target, new Map()); } EventMetadata.GetOwnLifecycleCallbackMap = GetOwnLifecycleCallbackMap; /** @description Gets own and inherited lifecycle callback data merged into a single Map. */ function GetLifecycleCallbackMap(target) { const metadata = new Map(); const ownMetadata = GetOwnLifecycleCallbackMap(target); ownMetadata.forEach((v, k) => { if (!metadata.has(k)) { metadata.set(k, []); } metadata.get(k).push(...v); }); const targetPrototype = Object.getPrototypeOf(target); if (targetPrototype) { const inherittedMetadata = GetLifecycleCallbackMap(targetPrototype); inherittedMetadata.forEach((v, k) => { if (!metadata.has(k)) { metadata.set(k, []); } metadata.get(k).push(...v); }); } return metadata; } EventMetadata.GetLifecycleCallbackMap = GetLifecycleCallbackMap; /** * @description * Gets the property subject map for the given event type from the metadata map for the given target class (or its inheritted classes). */ function GetPropertySubjectMap(type, target) { let table = GetEventSubjectTable(target); let subjectMap = table.get(type); if (!subjectMap) { subjectMap = new Map(); table.set(type, subjectMap); } return subjectMap; } EventMetadata.GetPropertySubjectMap = GetPropertySubjectMap; /** * @description * Gets the property subject map for the given event type from the metadata map for the given target class. */ function GetOwnPropertySubjectMap(type, target) { let table = GetOwnEventSubjectTable(target); let subjectMap = table.get(type); if (!subjectMap) { subjectMap = new Map(); table.set(type, subjectMap); } return subjectMap; } EventMetadata.GetOwnPropertySubjectMap = GetOwnPropertySubjectMap; function GetLifecycleCallbackList(target, type) { const map = GetLifecycleCallbackMap(target); return map.get(type) ?? []; } EventMetadata.GetLifecycleCallbackList = GetLifecycleCallbackList; function HasOwnEventSubjectTable(target) { return Metadata.hasOwnMetadata(EventSubjectTableSymbol, target); } EventMetadata.HasOwnEventSubjectTable = HasOwnEventSubjectTable; function SetEventSubjectTable(target, map) { Metadata.setMetadata(EventSubjectTableSymbol, target, map); } EventMetadata.SetEventSubjectTable = SetEventSubjectTable; function AddLifecycleCallback(target, type, callback) { const map = GetOwnLifecycleCallbackMap(target); if (!map.has(type)) { map.set(type, []); } const callbacks = map.get(type); callbacks.push(callback); Metadata.setMetadata(LifecycleCallbackMapSymbol, target, map); } EventMetadata.AddLifecycleCallback = AddLifecycleCallback; function RemoveLifecycleCallback(target, type, callback) { const map = GetOwnLifecycleCallbackMap(target); if (!map.has(type)) { return; } const callbacks = map.get(type); map.set(type, callbacks.filter(curCallback => curCallback !== callback)); Metadata.setMetadata(LifecycleCallbackMapSymbol, target, map); } EventMetadata.RemoveLifecycleCallback = RemoveLifecycleCallback; /** * @description Copy all metadata from the source map to the target map. * * Note: This mutates the target map. **/ function CopySubjectTable(target, source, overwrite) { // Iterate over all source metadata properties... source.forEach((propertySubjectMap, eventType) => propertySubjectMap.forEach((value, propertyKey) => { let targetPropertySubjectMap; // Get the property subject map (or create it if it doesn't exist for this eventType) if (target.has(eventType)) { targetPropertySubjectMap = target.get(eventType); } else { targetPropertySubjectMap = new Map(); target.set(eventType, targetPropertySubjectMap); } // And add them to this class' metadata map if not already defined if (overwrite || !targetPropertySubjectMap.has(propertyKey)) { targetPropertySubjectMap.set(propertyKey, Object.assign({}, value)); } })); return target; } EventMetadata.CopySubjectTable = CopySubjectTable; /** * @description Merge own and inheritted metadata into a single map. * * Note: This mutates the object's metadata. **/ function CopyInherittedSubjectTable(object) { if (object) { let subjectTable = GetEventSubjectTable(object); let inherittedTable = CopyInherittedSubjectTable(Object.getPrototypeOf(object)); // Merge own and inheritted metadata into a single map (note: this mutates object's metadata) return CopySubjectTable(subjectTable, inherittedTable); } return new Map(); } EventMetadata.CopyInherittedSubjectTable = CopyInherittedSubjectTable; })(EventMetadata || (EventMetadata = {})); /** @PropertyDecoratorFactory */ function AsyncState(asyncSource) { /** @PropertyDecorator */ return function (target, key) { const asyncKey = (asyncSource ?? asyncStateKey(key)); ComponentStateMetadata.AddManagedProperty(target.constructor, { key, asyncSource: asyncKey }); }; } var AutoPush; (function (AutoPush) { const CHANGE_DETECTOR_DATA = Symbol("cdRefData"); let ChangeDetectorProxy; (function (ChangeDetectorProxy) { function fromRef(ref, options) { return { doCheck() { if (options.forceDetectChanges) { ref.detectChanges(); } else { ref.markForCheck(); } } }; } ChangeDetectorProxy.fromRef = fromRef; })(ChangeDetectorProxy = AutoPush.ChangeDetectorProxy || (AutoPush.ChangeDetectorProxy = {})); function changeDetector(component) { const metadata = changeDetectorMetadata(component); return metadata ? metadata.changeDetector : undefined; } AutoPush.changeDetector = changeDetector; function enable(component, changeDetector, options = {}) { Metadata.setMetadata(CHANGE_DETECTOR_DATA, component, { options, changeDetector: isProxy(changeDetector) ? changeDetector : ChangeDetectorProxy.fromRef(changeDetector, options) }); } AutoPush.enable = enable; function notifyChanges(component) { // Check to see if AutoPush is enabled on this component const cdData = changeDetectorMetadata(component); if (cdData) { // Notify change detector that there were changes to a component value cdData.changeDetector.doCheck(); } } AutoPush.notifyChanges = notifyChanges; function isChangeDetectorLike(object) { return object && typeof object.detectChanges === "function"; } AutoPush.isChangeDetectorLike = isChangeDetectorLike; function changeDetectorMetadata(component) { return Metadata.getMetadata(CHANGE_DETECTOR_DATA, component); } function isProxy(input) { return input && typeof input.doCheck === "function"; } })(AutoPush || (AutoPush = {})); // Enable dynamic templating for Ivy-compiled components: /** @deprecated */ function TemplateDynamic() { return class TemplateDynamic { }; } /** @deprecated */ class LiComponent extends TemplateDynamic() { } // TODO fix generics when TypeScript mixin issue is fixed: https://github.com/Microsoft/TypeScript/issues/24122 function ManagedObservableWrapper /*<T, BaseObservable extends Observable<T>>*/($class) { class _Managed extends $class { componentInstance; subscriptions = new Subscription(); constructor(componentInstance, ...args) { super(...args); this.componentInstance = componentInstance; // Automatically handle unsubscribing on component's ngOnDestroy event this.subscriptions.add(componentInstance[CommonMetadata.MANAGED_ONDESTROY_KEY].subscribe(() => { // Mark the component instance as destroyed Metadata.setMetadata(CommonMetadata.MANAGED_INSTANCE_DESTROYED_KEY, this.componentInstance, true); this.subscriptions?.unsubscribe(); this.subscriptions = undefined; this.componentInstance = undefined; if (this instanceof Subject) { this.complete(); } })); } subscribe(...args) { if (this.componentInstance && !CommonMetadata.instanceIsDestroyed(this.componentInstance)) { const subscription = super.subscribe(...args); // Manage new subscription this.subscriptions.add(subscription); return subscription; } else { return EMPTY.subscribe(); } } } ; return _Managed; } class ManagedObservable extends ManagedObservableWrapper(Observable) { constructor(componentInstance, subscribe) { super(componentInstance, subscribe); } } class ManagedSubject extends ManagedObservableWrapper(Subject) { constructor(componentInstance) { super(componentInstance); } } class ManagedBehaviorSubject extends ManagedObservableWrapper(BehaviorSubject) { constructor(componentInstance, initialValue) { super(componentInstance, initialValue); } } class ManagedReplaySubject extends ManagedObservableWrapper(ReplaySubject) { constructor(componentInstance, bufferSize, windowTime, scheduler) { super(componentInstance, bufferSize, windowTime, scheduler); } } var AngularLifecycleType; (function (AngularLifecycleType) { AngularLifecycleType["OnChanges"] = "ngOnChanges"; AngularLifecycleType["OnInit"] = "ngOnInit"; AngularLifecycleType["OnDestroy"] = "ngOnDestroy"; AngularLifecycleType["DoCheck"] = "ngDoCheck"; AngularLifecycleType["AfterContentInit"] = "ngAfterContentInit"; AngularLifecycleType["AfterContentChecked"] = "ngAfterContentChecked"; AngularLifecycleType["AfterViewInit"] = "ngAfterViewInit"; AngularLifecycleType["AfterViewChecked"] = "ngAfterViewChecked"; })(AngularLifecycleType || (AngularLifecycleType = {})); ; (function (AngularLifecycleType) { AngularLifecycleType.values = [ AngularLifecycleType.OnChanges, AngularLifecycleType.OnInit, AngularLifecycleType.OnDestroy, AngularLifecycleType.DoCheck, AngularLifecycleType.AfterContentInit, AngularLifecycleType.AfterContentChecked, AngularLifecycleType.AfterViewInit, AngularLifecycleType.AfterViewChecked ]; })(AngularLifecycleType || (AngularLifecycleType = {})); /** @PropertyDecoratorFactory */ function EventSource(...args) { let paramsArg; if (args.length > 0) { paramsArg = args[0]; } if (!paramsArg || paramsArg instanceof Function) { return EventSource.WithParams(undefined, ...args); } else { return EventSource.WithParams(paramsArg, ...args.slice(1)); } } (function (EventSource) { /** @PropertyDecoratorFactory */ function WithParams(options, ...methodDecorators) { options ??= {}; /** @PropertyDecorator */ return function (target, propertyKey) { if (propertyKey !== CommonMetadata.MANAGED_ONDESTROY_KEY && !options.unmanaged) { // Ensure that we create a ngOnDestroy EventSource on the target for managing subscriptions WithParams({ eventType: AngularLifecycleType.OnDestroy })(target, CommonMetadata.MANAGED_ONDESTROY_KEY); } // If an eventType wasn't specified... if (!options.eventType) { // Try to deduce the eventType from the propertyKey if (typeof propertyKey === "string" && propertyKey.endsWith("$")) { options.eventType = propertyKey.substring(0, propertyKey.length - 1); } else { throw new Error(`@EventSource error: eventType could not be deduced from propertyKey "${propertyKey}" (only keys ending with '$' can be auto-deduced).`); } } // Create the event source metadata for the decorated property createMetadata(options, target, propertyKey); // Apply any method decorators to the facade function methodDecorators.forEach(methodDecorator => methodDecorator(target, options.eventType, Object.getOwnPropertyDescriptor(target, options.eventType))); }; } EventSource.WithParams = WithParams; function bootstrapInstance(eventType, isLifecycleEvent) { const targetInstance = this; if (!isLifecycleEvent) { // Assign the facade function for the given event type to the target instance Facade.CreateAndAssign(eventType, targetInstance); } function classSubjectTableMerged(merged) { if (merged === undefined) { return !!Metadata.getMetadata(EventMetadata.SUBJECT_TABLE_MERGED_KEY, targetInstance); } else { Metadata.setMetadata(EventMetadata.SUBJECT_TABLE_MERGED_KEY, targetInstance, merged); } return undefined; } const subjectTable = EventMetadata.GetOwnEventSubjectTable(targetInstance); if (!classSubjectTableMerged()) { // Copy all event metadata from the class constructor to the target instance EventMetadata.CopySubjectTable(subjectTable, EventMetadata.CopyInherittedSubjectTable(targetInstance.constructor), true); classSubjectTableMerged(true); } const propertySubjectMap = subjectTable.get(eventType); // Iterate over each of the target properties for each proxied event type used in this class propertySubjectMap?.forEach((subjectInfo, propertyKey) => { // If the event proxy subject hasn't been created for this property yet... if (!subjectInfo.subject) { // Create a new Subject if (subjectInfo.unmanaged || propertyKey === CommonMetadata.MANAGED_ONDESTROY_KEY) { subjectInfo.subject = new Subject(); } else { subjectInfo.subject = new ManagedSubject(targetInstance); } } // Set the property key to a function that will invoke the facade method when called // (This is needed to allow EventSources to work with Angular event decorators like @HostListener) // Compose the function with the observable // TODO - Figure out a better way to do this with Ivy let propertyValue = Object.setPrototypeOf(Facade.Create(eventType), subjectInfo.subject); Object.defineProperty(targetInstance, propertyKey, { get: () => propertyValue }); }); EventMetadata.GetInstanceBootstrapMap(targetInstance).set(eventType, true); } let Facade; (function (Facade) { /** @description * Creates an event facade function (the function that is invoked during an event) for the given event type. */ function Create(eventType) { return Object.assign(function (...values) { // Get the list of subjects to notify for this `eventType` const subjectInfoList = Array.from(EventMetadata.GetPropertySubjectMap(eventType, this).values()); // Use the first value from this event if only a single value was given, otherwise emit all given values as an array to the Subject const valueToEmit = (values.length > 1) ? values : (values.length > 0) ? values[0] : undefined; // Iterate in reverse order for ngOnDestroy eventTypes. // This ensures that all user-defined OnDestroy EventSources are fired before final cleanup of subscriptions. if (eventType === "ngOnDestroy") { subjectInfoList.reverse(); } // Emit the given event value to each interested subject subjectInfoList .filter(subjectInfo => !!subjectInfo.subject) .forEach(subjectInfo => subjectInfo.subject.next(valueToEmit)); }, { eventType }); } Facade.Create = Create; function CreateAndAssign(eventType, instance) { // Assign the facade function for the given event type to the appropriate target class method // This function gets called from the view template and triggers the associated Subject Object.defineProperty(instance, eventType, { enumerable: true, value: Create(eventType) }); } Facade.CreateAndAssign = CreateAndAssign; })(Facade || (Facade = {})); function createMetadata(options, target, propertyKey) { const ContainsCustomMethod = ($class = target) => { const methodDescriptor = Object.getOwnPropertyDescriptor($class, options.eventType); const method = methodDescriptor ? (methodDescriptor.value || methodDescriptor.get) : undefined; const isCustomMethod = method && method.eventType !== options.eventType; return isCustomMethod || (!method && target.prototype && ContainsCustomMethod(target.prototype)); }; // Determine if this EventSource is handling an Angular lifecycle event const isLifecycleEvent = AngularLifecycleType.values.includes(options.eventType); if (!options.skipMethodCheck && ContainsCustomMethod()) { // Make sure the target class doesn't have a custom method already defined for this event type throw new Error(`@EventSource metadata creation failed. Class already has a custom ${options.eventType} method.`); } // Add ths EventSource definition to the class' metadata EventMetadata.GetOwnPropertySubjectMap(options.eventType, target.constructor).set(propertyKey, options); if (isLifecycleEvent) { registerLifecycleEventFacade(target.constructor, options.eventType); } // Initialize the propertyKey on the target to a self-bootstrapper that will initialize an instance's EventSource when called Object.defineProperty(target, propertyKey, { configurable: true, get: function () { // Ensure we only bootstrap once for this `eventType` if the intializer is re-invoked (Ivy) if (!isBootstrapped.call(this, options.eventType)) { // Boostrap the event source for this instance bootstrapInstance.bind(this)(options.eventType, isLifecycleEvent); } // Return the Observable for the event return this[propertyKey]; } }); // Only initialize a bootstrapper function for the eventType if this isn't a lifecycle event (otherwise Ivy will handle it) if (!isLifecycleEvent) { // Set the eventType on the target to a self-bootstrapper function that will initialize an instance's EventSource when called Object.defineProperty(target, options.eventType, { configurable: true, writable: true, value: Object.assign(function (...args) { // Ensure we only bootstrap once for this `eventType` if the intializer is re-invoked (Ivy) if (!isBootstrapped.call(this, options.eventType)) { // Boostrap the event source for this instance bootstrapInstance.bind(this)(options.eventType); } // Invoke the facade function for the event return this[options.eventType].call(this, ...args); }, { eventType: options.eventType }) }); } } function registerLifecycleEventFacade(targetClass, eventType) { const registrationMap = EventMetadata.GetLifecycleRegistrationMap(targetClass); // Register the facade function for this component lifecycle target if we haven't already if (!registrationMap.get(eventType)) { const ownRegistrationMap = EventMetadata.GetOwnLifecycleRegistrationMap(targetClass); registerLifecycleEvent(targetClass, eventType, Facade.Create(eventType)); ownRegistrationMap.set(eventType, true); } } /** * @description Registers a lifecycle event handler for use with `registerPreOrderHooks`/`registerPostOrderHooks` */ function registerLifecycleEvent(targetClass, eventType, hookFn) { EventMetadata.AddLifecycleCallback(targetClass, eventType, hookFn); // Ensure a valid prototype exists for this component if (!targetClass.prototype) { targetClass.prototype = Object.create({}); } // Get the name of the hook for this lifecycle event const hookName = eventType; // Store a reference to the original hook function const prevLifecycleHook = targetClass.prototype[hookName]; // Replace the default lifecycle hook with a modified one that ensures the given hook fns are invoked if (!prevLifecycleHook?.eventType) { targetClass.prototype[hookName] = Object.assign(function (...args) { // Call the previous hook function on the component instance if there is one if (prevLifecycleHook) { prevLifecycleHook.call(this, ...args); } // Invoke all of the hook functions associated with this lifeycle event for the current component instance const hookFns = EventMetadata.GetLifecycleCallbackList(this.constructor, eventType); hookFns.forEach(hookFn => hookFn.call(this, ...args)); }, { eventType }); } } EventSource.registerLifecycleEvent = registerLifecycleEvent; function unregisterLifecycleEvent(targetClass, eventType, hookFn) { EventMetadata.RemoveLifecycleCallback(targetClass, eventType, hookFn); } EventSource.unregisterLifecycleEvent = unregisterLifecycleEvent; function isBootstrapped(eventType) { const map = EventMetadata.GetInstanceBootstrapMap(this); return map.has(eventType) ? map.get(eventType) : false; } })(EventSource || (EventSource = {})); const COMPONENT_STATE_IDENTITY = Symbol("COMPONENT_STATE_IDENTITY"); class ComponentStateRef extends Promise { componentInstance; /** * @description Resolves the `ComponentState` instance for this reference. * @returns An `Observable` that emits the `ComponentState` instance for this reference. */ state() { return from(this); } /** * @description Returns an `Observable` that represents the current value of the given state property and emits whenever the value of the given state * property is changed. * @param stateProp - The state property to observe. * @returns An `Observable` that emits the value of the given state property and re-emits when the value is changed. */ get(stateProp) { const stateKey = ComponentState.stateKey(stateProp); const resolvedSource$ = this.resolvedState?.[stateKey]; if (resolvedSource$) { return resolvedSource$; } else { return this.state().pipe(mergeMap((state) => { if (!state[stateKey]) { return throwError(`[ComponentStateRef] Failed to get state for component property "${stateProp}". Ensure that this property is explicitly initialized (or declare it with @DeclareState()).`); } return state[stateKey]; })); } } /** * @description Returns an array of `Observable`s that represents the current value for each given state property. Each `Observable` emits whenever a * value of the corresponding given state property is changed. * @param stateProps - The state properties to observe. * @returns An array of `Observable`s that represents the current value for each given state property and re-emits when the corresponding value is * changed. */ getAll(...stateProps) { return stateProps.map(stateProp => this.get(stateProp)); } /** * @description Returns an `EventEmitter` that emits whenever the value of the given state property is changed. * @param stateProp - The state property to observe. * @returns An `EventEmitter` instance that emits whenever the value of the given state property is changed. */ emitter(stateProp) { const emitter$ = new EventEmitter(); this.get(stateProp) .pipe(skip(1)) .subscribe({ next: value => emitter$.emit(value), error: err => emitter$.error(err) }); return emitter$; } /** * @description Updates the value of the given state property with the given value. Equivalent to assigning to the component state property directly. * @param stateProp - The state property to update. This property must not be readonly. * @param value - The new value to update to. * @returns An `Observable` that emits and completes when the value has been updated. */ set(stateProp, value) { const stateKey = ComponentState.stateKey(stateProp); const result$ = new ReplaySubject(1); const resolvedSource$ = this.resolvedState?.[stateKey]; if (resolvedSource$) { resolvedSource$.next(value); result$.next(); result$.complete(); } else { this.state().pipe(map((state) => { const stateSubject$ = state[ComponentState.stateKey(stateProp)]; if (!stateSubject$) { throw new Error(`[ComponentStateRef] Failed to set state for component property "${stateProp}". Ensure that this property is explicitly initialized (or declare it with @DeclareState()).`); } return stateSubject$; })).subscribe((stateSubject$) => { stateSubject$.next(value); result$.next(); result$.complete(); }, (e) => { result$.error(e); result$.complete(); }, () => result$.complete()); } return result$; } /** * @description Subscribes the given state property to the given source `Observable`. If `managed` is set to true, the lifetime of the subscription will * be managed and cleaned up when the component is destroyed. * @param stateProp - The state property to receive source updates. This property must not be readonly. * @param source$ - The source `Observable` to subscribe to. * @param managed - Whether or not the subscription lifetime should be managed. Defaults to `true`. * @returns A `Subscription` representing the subscription to the source. */ subscribeTo(stateProp, source$, managed = true) { let managedSource$; if (managed) { managedSource$ = this.state().pipe(mergeMap(() => _createManagedSource(source$, this.componentInstance))); } else { managedSource$ = source$; } return managedSource$.pipe(tap(sourceValue => this.set(stateProp, sourceValue))).subscribe(); } /** * @description Synchronizes the values of the given state properties such that any changes from one state property will be propagated to the * other state property. The initial value of the first given state property is used. * @param statePropA - The first state property to synchronize. This property must not be readonly. * @param statePropB - The second state property to synchronize. This property must not be readonly. */ sync(statePropA, statePropB) { let syncing = false; merge(this.get(statePropB), this.get(statePropA)).pipe(skip(1), distinctUntilChanged(), filter(() => !syncing), tap(() => syncing = true), mergeMap((value) => combineLatest([ this.set(statePropA, value), this.set(statePropB, value) ])), tap(() => syncing = false)).subscribe(); } syncWith(stateProp, source, sourceProp) { let syncing = false; this.state().pipe(switchMap(() => merge(this.get(stateProp).pipe(skip(1)), source instanceof Subject ? _createManagedSource(source, this.componentInstance) : source.get(sourceProp))), distinctUntilChanged(), filter(() => !syncing), tap(() => syncing = true), mergeMap((value) => { return forkJoin([ source instanceof Subject ? of(source.next(value)) : source.set(sourceProp, value), this.set(stateProp, value) ]); }), tap(() => syncing = false)).subscribe(); } get resolvedState() { return this.componentInstance?.[COMPONENT_STATE_IDENTITY]; } } var ComponentState; (function (ComponentState) { function create($class, options) { return createComponentState($class, options); } ComponentState.create = create; function createFactory($class, options) { options ??= { lazy: isForwardRef($class) }; if (!options.lazy) { if (isForwardRef($class)) { throw new Error("[ComponentState] A component state created with forwardRef must be created with the `lazy` flag."); } const resolvedClass = resolveClass($class); // Generate initial component state on ngOnInit updateStateOnEvent(resolvedClass, AngularLifecycleType.OnInit); // Update the component state on afterViewInit and afterContentInit to capture dynamically initialized properties updateStateOnEvent(resolvedClass, AngularLifecycleType.AfterContentInit); updateStateOnEvent(resolvedClass, AngularLifecycleType.AfterViewInit); } return function (injector) { const stateRef = new ComponentStateRef((resolve) => { const resolvedClass = resolveClass($class); const delayedInitializer = setTimeout(() => { // If the stateRef has not been initialized by the end of the current execution frame (e.g. the service was // injected after component's lifecycle events were invoked), we need to resolve it now. const instance = injector.get(resolvedClass); stateRef.componentInstance = instance; updateState(_requireComponentState(instance), instance); // Resolve the component state resolve(instance[COMPONENT_STATE_IDENTITY]); }); if (options.lazy) { // Generate initial component state on ngOnInit updateStateOnEvent(resolvedClass, AngularLifecycleType.OnInit, injector, (instance) => { clearTimeout(delayedInitializer); stateRef.componentInstance = instance; }); // Update the component state on afterViewInit and afterContentInit to capture dynamically initialized properties updateStateOnEvent(resolvedClass, AngularLifecycleType.AfterContentInit, injector); updateStateOnEvent(resolvedClass, AngularLifecycleType.AfterViewInit, injector, (instance) => { clearTimeout(delayedInitializer); // Resolve the component state resolve(instance[COMPONENT_STATE_IDENTITY]); }); } else { updateOnEvent(resolvedClass, AngularLifecycleType.OnInit, (instance) => { clearTimeout(delayedInitializer); stateRef.componentInstance = instance; }, injector); updateOnEvent(resolvedClass, AngularLifecycleType.AfterViewInit, (instance) => { clearTimeout(delayedInitializer); // Resolve the component state resolve(instance[COMPONENT_STATE_IDENTITY]); }, injector); } }); return stateRef; }; } ComponentState.createFactory = createFactory; function tokenFor(provider) { return stateTokenFor(provider); } ComponentState.tokenFor = tokenFor; function stateKey(key) { return asyncStateKey(key); } ComponentState.stateKey = stateKey; function updateStateOnEvent($class, event, injector, onComplete) { updateOnEvent($class, event, (instance) => { updateState(_requireComponentState(instance), instance); if (onComplete) { onComplete(instance); } }, injector); } function updateOnEvent($class, event, onUpdate, injector) { onEvent($class, event, function onEventFn() { const instance = injector ? injector.get($class, null, { self: true }) : this; if (instance === this) { onUpdate(instance); // Only de-register instance-specific event handlers if (injector) { offEvent($class, event, onEventFn); } } }); } function onEvent($class, event, callback) { // Ensure that we create a OnDestroy EventSource on the target for managing subscriptions EventSource({ eventType: AngularLifecycleType.OnDestroy })($class.prototype, CommonMetadata.MANAGED_ONDESTROY_KEY); // Register a lifecycle event listener for the given event EventSource.registerLifecycleEvent($class, event, callback); } function offEvent($class, event, callback) { EventSource.unregisterLifecycleEvent($class, event, callback); } function updateState(componentState, instance) { const instanceProps = getAllAccessibleKeys(instance); // Create a managed reactive state wrapper for each component property instanceProps.forEach((prop) => { // Only update an entry if it hasn't yet been defined if (!componentState[ComponentState.stateKey(prop.key)]) { updateStateForProperty(componentState, instance, prop); } }); return componentState; } function updateStateForProperty(componentState, instance, prop) { const propDescriptor = Object.getOwnPropertyDescriptor(instance, prop.key); const stateSubjectProp = stateKey(prop.key); if (typeof prop.key === "string" && !prop.key.endsWith("$") && !EmitterMetadata.GetMetadataMap(instance).get(prop.key)) { if (!propDescriptor || propDescriptor.configurable) { let lastValue = instance[prop.key]; const propSubject$ = new ManagedBehaviorSubject(instance, lastValue); function manageProperty(instance, property, enumerable) { const stateProp = stateKey(property); componentState[stateProp] = propSubject$; // Override the instance property with a getter/setter that synchronizes with `propSubject$` Object.defineProperty(instance, property, { configurable: true, enumerable: enumerable, get: () => lastValue, set: isReadonlyProperty(instance, property) ? undefined : (newValue) => propSubject$.next(newValue) }); } // Monitor the property subject for value changes propSubject$.pipe(skip(1)).subscribe(value => { // Update the cached value lastValue = value; // Notify the component of changes if AutoPush is enabled AutoPush.notifyChanges(instance); }); if (prop.asyncSource) { const reactiveSource$ = instance[prop.asyncSource]; // If the property has a valid async source, create a managed subscription to it if (reactiveSource$ && reactiveSource$ instanceof Observable) { _createManagedSource(reactiveSource$, instance) .subscribe((value) => propSubject$.next(value)); } } // Set up the property wrapper that exposes the backing subject try { manageProperty(instance, prop.key, !propDescriptor || !!propDescriptor.enumerable); // If a separate publicKey was defined, also map it to the backing subject if (prop.publicKey && prop.publicKey !== prop.key) { manageProperty(instance, prop.publicKey, true); } } catch (e) { console.error(`Failed to create state Subject for property ${instance.constructor.name}.${prop.key}`, e); } } else { if (!propDescriptor.configurable && !isReadonlyProperty(instance, prop.key)) { console.warn(`[ComponentState] Property "${instance.constructor.name}.${prop.key}" is not configurable and will be treated as readonly.`); } // Property is readonly, so just use an Observable that emits the underlying state on subscription componentState[stateSubjectProp] = new ManagedObservable(instance, observer => { observer.next(propDescriptor.get ? propDescriptor.get() : propDescriptor.value); }); } } return componentState; } function isReadonlyProperty(instance, key) { const publicPropDescriptor = Object.getOwnPropertyDescriptor(instance, key); return !!publicPropDescriptor && !publicPropDescriptor.writable && !publicPropDescriptor.set; } function isForwardRef($class) { return !$class.name; } function resolveClass($class) { return resolveForwardRef($class); } function getAllAccessibleKeys(instance) { // Ensure managed keys are processed first return getManagedKeys(instance).concat(getPublicKeys(instance)); } function getPublicKeys(instance) { re