import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; import { Property } from './Interfaces'; import { handleError, isObservable, isSubject } from './Utils'; export class ObservableProperty extends Subscription implements Property { protected changedSubject: BehaviorSubject; protected thrownErrorsSubject: Subject; constructor( initialValue?: T, compare?: (x: T, y: T) => boolean, keySelector?: (x: T) => any, protected source: Observable = new Subject(), ) { super(); this.changedSubject = this.addSubscription( new BehaviorSubject(initialValue!), ); this.thrownErrorsSubject = this.addSubscription(new Subject()); this.add( this.source // seed the observable subscription with the initial value so that // distinctUntilChanged knows what the initial value is .startWith(initialValue!) .distinctUntilChanged(compare!, keySelector!) .subscribe( x => { this.changedSubject.next(x); }, e => { handleError(e, this.thrownErrorsSubject); }, ), ); } get isReadOnly() { return isSubject(this.source) === false; } get value() { return this.changedSubject.getValue(); } set value(newValue: T) { if (isSubject(this.source)) { this.source.next(newValue); } else { throw new Error('attempt to write to a read-only observable property'); } } get changed() { return ( this.changedSubject // BehaviorSubject fires immediately, so skip the first event .skip(1) ); } get thrownErrors() { return this.thrownErrorsSubject.asObservable(); } isProperty() { return true; } } export function property( initialValue?: T, source?: Observable, ): Property; export function property( initialValue?: T, compare?: boolean | ((x: T, y: T) => boolean), source?: Observable, ): Property; export function property( initialValue?: T, compare?: boolean | ((x: T, y: T) => boolean), keySelector?: (x: T) => any, source?: Observable, ): Property; export function property(...args: any[]): Property { const initialValue: T = args.shift(); let compare: undefined | ((x: T, y: T) => boolean); let keySelector: undefined | ((x: T) => any); let source: undefined | Observable; let arg = args.shift(); if (isObservable(arg)) { source = arg; } else { // if arg is true, then we use the default comparator function if (arg === false) { // this results in every value being interpreted as a new value compare = () => false; } else if (arg instanceof Function) { compare = arg; } } arg = args.shift(); if (isObservable(arg)) { source = arg; } else { keySelector = arg; } arg = args.shift(); if (isObservable(arg)) { source = arg; } return new ObservableProperty(initialValue, compare, keySelector, source); }