All files action.ts

96.77% Statements 60/62
91.67% Branches 22/24
94.12% Functions 16/17
96.55% Lines 56/58
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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 1701x 1x 1x 1x 1x 1x 1x   1x     1x   1x 1x                                                   1x       1x 1x                   1x             1x 11x                 1x 46x 46x 5x 5x   46x                   1x 13x 4x   13x 13x                 16x   16x 16x 16x 1x     15x       53x 53x 50x 50x 50x     3x         54x 52x         54x   2x 52x   15x         52x       16x 12x   16x           15x   15x       1x     1x 14x     1x  
import 'rxjs/add/observable/empty'
import 'rxjs/add/observable/from'
import 'rxjs/add/operator/catch'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/mergeMap'
import 'rxjs/add/operator/share'
import 'rxjs/add/operator/skipWhile'
 
import * as Immutable from 'seamless-immutable'
 
import { ActionObserver } from './observers'
import { Observable } from 'rxjs/Observable'
import { Observer } from 'rxjs/Observer'
import { ReplaceableState } from './replaceable-state'
import { State } from './state'
 
/**
 * Defines an action which an be extended to implement custom actions for a statex application
 *
 * @example
 *
 * // Create your own action class
 * class PageSwitchAction extends Action {
 *   constructor(public pageId: string) { super() }
 * }
 *
 * // Subscribe to your action
 * new PageSwitchAction(undefined).subscribe((state: State, action: PageSwitchAction): Observable<State> => {
 *   return Observable.create((observer: Observer<State>) => {
 *     observer.next(updatedState)
 *       observer.complete()
 *   }).share()
 * }, this)
 *
 * // Dispatch your action
 * new PageSwitchAction('page1').dispatch()
 *
 * @export
 * @class Action
 */
export class Action {
 
  private static _lastAction: Action
  private static _showError: boolean
  private static identities: any[] = []
  private static subscriptions: any[] = []
 
  /**
   * The last action occurred
   *
   * @readonly
   * @static
   *
   * @memberOf Action
   */
  public static get lastAction() {
    return Action._lastAction
  }
 
  /**
   * Set show error flag, if set to true, this module must show errors on Action rejections
   */
  public static set showError(showError: boolean) {
    Action._showError = showError
  }
 
  /**
   * Returns identity of this class
   *
   * @readonly
   * @type {string}
   */
  get identity(): string {
    let id = Action.identities.indexOf(this.constructor)
    if (id < 0) {
      Action.identities.push(this.constructor)
      id = Action.identities.indexOf(this.constructor)
    }
    return `c${id}`
  }
 
  /**
   * Subscribe to this action. actionObserver will be called when 'dispatch()' is invoked
   *
   * @param {ActionObserver} actionObserver The function that process the action
   * @param {*} context Context binding
   * @returns {Action}
   */
  public subscribe(actionObserver: ActionObserver, context: any): Action {
    if (!Action.subscriptions[this.identity]) {
      Action.subscriptions[this.identity] = []
    }
    Action.subscriptions[this.identity].push(actionObserver.bind(context))
    return this
  }
 
  /**
   * Dispatch this action. Returns an observable which will be completed when all action subscribers
   * complete it's processing
   *
   * @returns {Observable<S>}
   */
  dispatch(): Promise<any> {
 
    Action._lastAction = this
    let subscriptions: ActionObserver[] = Action.subscriptions[this.identity]
    if (subscriptions == undefined || subscriptions.length === 0) {
      return new Promise(resolve => resolve())
    }
 
    let observable: Observable<any> = Observable.from(subscriptions)
 
      // convert 'Observable' returned by action subscribers to state
      .flatMap((actionObserver: ActionObserver): Observable<any> => {
        const result = actionObserver(State.current, this)
        if (!(result instanceof Observable || result instanceof Promise)) {
          return Observable.create((observer: Observer<any>) => {
            observer.next(result)
            observer.complete()
          })
        }
        return result as Observable<any>
      })
 
      // if reducer returns function call that function to resolve state
      .map((state: any) => {
        if (typeof state === 'function') return state(State.current)
        return state
      })
 
      // merge or replace state
      .map((state: any) => {
        if (state instanceof ReplaceableState) {
          // replace the state with the new one if not 'undefined'
          return Immutable.from((state as ReplaceableState).state || {})
        } else if (state != undefined) {
          // merge the state with existing state
          return State.current.merge(state, { deep: true })
        }
      })
 
      // wait until all the subscripts have completed processing
      .skipWhile((state: any, i: number) => i + 1 < subscriptions.length)
 
      // push 'next' state to 'stateStream' if there has been a change to the state
      .map((state: any) => {
        if (state != undefined) {
          State.next(state)
        }
        return state
      })
 
      // make this sharable (to avoid multiple copies of this observable being created)
      .share()
 
    return new Promise((resolve, reject) => {
      // to trigger observable
      observable.subscribe(() => {
        // empty function
      }, (error) => {
        // show error
        Iif (Action._showError) {
          console.error(error)
        }
        reject(error)
      }, () => resolve(State.current))
    })
  }
}