UNPKG

reactive-observables

Version:

Framework-agnostic observables and computed properties similar to Knockout and Vue, built on RxJS

80 lines (58 loc) 2.86 kB
import { createTracked } from "../factories"; import { TrackedTypeForValue } from "../trackable"; import { Subscription, merge } from "rxjs"; import { getTrackedPropertyDefinitionList } from "./tracked-property-definition"; import { getComputedProperty } from "./computed-property"; interface TrackedProperty<T> { subject: TrackedTypeForValue<T>; } type TrackedPropertyList<T> = { [K in keyof T]: TrackedProperty<T[K]>; } const TrackedPropertyListKey = "_trackedProperties"; export const getTrackedPropertyList = <T>(obj: T) => ((obj as any)[TrackedPropertyListKey] = (obj as any)[TrackedPropertyListKey] || {}) as TrackedPropertyList<T>; export const getTrackedProperty = <T, K extends keyof T>(obj: T, name: K, notFound?: (list: TrackedPropertyList<T>, name: K) => TrackedTypeForValue<T[K]>) => { const list = getTrackedPropertyList(obj); const existing = list[name]; if (!existing) { if (notFound) return notFound(list, name) else throw new Error("Property accessed before initialized or property doesn't exist: " + name); } return existing.subject; } // Moved here to avoid circular dependencies export const getOrSetupTrackedProperty = <T, K extends keyof T>(obj: T, name: K, value: T[K]): TrackedTypeForValue<T[K]> => { return getTrackedProperty(obj, name, list => (list[name] = { subject: createTracked(value as T[K]) }).subject); } export const getAllTrackedProperties = <T>(obj: T) => { const list = getTrackedPropertyDefinitionList(obj) return Object.keys(list) .map(key => ({ key, definition: list[key as keyof T] })) .map(({key, definition}) => { // // If someone tries to subscribe to a tracked entity without having read a computed, // // The computed will not yet be initialized and the application will throw // // Merely reading the property ensures getOrSetupComputed gets called and property is initialized // // TODO: Write a test which subscribes to a tracked entity with a computed without having called or read the // // computed in the constructor first (all existing tests read the computed in the constructor as part of the test) // obj[key as keyof T]; // 4-18-2020 moved to getComputedProperty; return definition.computed ? getComputedProperty(obj, key as keyof T) : getTrackedProperty(obj, key as keyof T) }) } export const getAllObservables = <T>(obj: T) => { return getAllTrackedProperties(obj) .map(tracked => tracked.observable); } export const subscribeAll = <T>(obj: T, observer: (values: any) => void): Subscription => { return merge(...getAllObservables(obj)) .subscribe(observer); }