rpora
10/6/2016 - 7:11 AM

Observable

/**
 * Usage
 *
 * const model = {
 *    username: new Observable<string>('foo'),
 *    age: new Observable<number>(45)
 * }
 *
 * const disposeUsernameObserver = model.username.addObserver((next, prev) => console.log(prev, next))
 * model.username.value = 'antoher one'
 *
 * // later
 * disposeUsernameObserver()
 *
 */

interface Observer<T> {
  (nextValue: T, prevValue: T): void
}

class Observable<T> {

  private observers: Observer<T>[] = []

  constructor(private _value: T) { }

  get value(): T {
    return this._value
  }

  set value(nextValue: T) {
    if (nextValue !== this._value) {
      this.notifyObservers(nextValue, this.value)
      this._value = nextValue
    }
  }

  public addObserver(observer: Observer<T>): Function {
    this.observers = [...this.observers, observer]
    return () => this.removeObserver(observer)
  }

  public removeObserver(observer: Observer<T>): Observable<T> {
    this.observers = this.observers.filter(obs => obs !== observer)
    return this
  }

  private notifyObservers(nextValue, prevValue) {
    this.observers.forEach(observer => observer(nextValue, prevValue))
  }

}