All files / core state.ts

100% Statements 30/30
100% Branches 6/6
100% Functions 10/10
100% Lines 26/26
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                                3x   3x         3x 110x             3x 57x                 3x 12x                   7x   7x   7x 31x 31x 22x 22x       7x       3x 3x 3x 60x     3x                   62x 51x 48x 48x   1x      
import * as Immutable from 'seamless-immutable'
 
import 'rxjs/add/operator/share'
import { BehaviorSubject } from 'rxjs/BehaviorSubject'
import { Observable } from 'rxjs/Observable'
import { StateSelector } from './state-selector'
import { Subscription } from 'rxjs/Subscription'
 
/**
 * Defines a stream for changing state in a statex application
 *
 * @example
 *
 * // replace state
 * State.next(state)
 *
 * // subscribe to state stream
 * State.subscribe((state: State) => {
 *   // do your action here
 * })
 *
 * // or listen to a portion of the state
 * State
 *   .select((state: State) => state.application.pageContainer)
 *   .subscribe((state: State) => {
 *     // do your action here
 *   })
 *
 * @export
 * @class StateStream
 * @extends {BehaviorSubject}
 */
export class State {
 
  private static state: State = new State()
 
  private currentState: any
  private subject: BehaviorSubject<any>
 
  static get current() {
    return State.state.currentState
  }
 
  /**
   * Publish next state
   * @param state
   */
  static next(state) {
    State.state.subject.next(state)
  }
 
  /**
   * Subscribe to the stream
   * @param onNext
   * @param onError
   * @param onComplete
   */
  static subscribe(onNext, onError, onComplete): Subscription {
    return State.state.subject.subscribe(onNext, onError, onComplete)
  }
 
  /**
   * Fires 'next' only when the value returned by this function changed from the previous value.
   *
   * @template T
   * @param {StateSelector<T>} selector
   * @returns {Observable<T>}
   */
  static select(selector: StateSelector): Observable<any> {
 
    return Observable.create(subscriber => {
      let previousState: any
      let subscription = this.subscribe(state => {
        let selection = select(state, selector)
        if (selection !== select(previousState, selector)) {
          previousState = state
          subscriber.next(selection)
        }
      }, undefined, undefined)
 
      return subscription
    }).share()
  }
 
  constructor() {
    this.currentState = Immutable.from<any>({})
    this.subject = new BehaviorSubject(this.currentState)
    this.subject.subscribe(state => this.currentState = state)
  }
 
}
 
/**
 * Run selector function on the given state and return it's result. Return undefined if an error occurred
 *
 * @param {*} state
 * @param {StateSelector} selector
 * @returns The value return by the selector, undefined if an error occurred.
 */
function select(state: any, selector: StateSelector) {
  if (state == undefined) return
  if (selector == undefined) return state
  try {
    return selector(state)
  } catch (error) {
    return undefined
  }
}