reactive-observables
Version:
Framework-agnostic observables and computed properties similar to Knockout and Vue, built on RxJS
80 lines (58 loc) • 2.86 kB
text/typescript
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);
}