UNPKG

rxjs-loading-state

Version:

Eliminates manual state management for loading and error states by transforming Observables into a LoadingState

161 lines (160 loc) 4.68 kB
import { BehaviorSubject } from "rxjs"; import { LoadingState } from "./loading-state"; class IllegalStateTransitionError extends Error { constructor(message) { super(message); this.name = "IllegalStateTransitionError"; Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain } } /** * Handles transitions between different loading state and holds the context data that is related to the current state. * @class LoadingStateMachine */ export class LoadingStateMachine { _state$; _data; _error; constructor() { this._state$ = new BehaviorSubject(LoadingState.NotStarted); } /** * Creates a new observable that represents the current state of the machine * * @returns {Observable<LoadingState>} Observable that emits the machine state */ asObservable() { return this._state$.asObservable(); } /** * Data of the current state. Depending on the current state, this may be undefined. */ get data() { return this._data; } /** * Error of the current state. Depending on the current state, this may be undefined. */ get error() { return this._error; } /** * The current LoadingState */ get state() { return this._state$.getValue(); } /** * Update data while in loading state * @param {T} newData */ update(newData) { if (!this.isLoading()) { throw new Error(`Update is only allowed during ${LoadingState.Loading} state`); } this._data = newData; this._state$.next(LoadingState.Loading); } /** * Starts loading */ start() { if (this.isLoading()) { throw new IllegalStateTransitionError(`Transition from ${this.state} to ${LoadingState.Loading} not allowed`); } this._state$.next(LoadingState.Loading); } /** * Transition to success state * @param {T} data */ succeed(data) { if (!this.isLoading()) { throw new IllegalStateTransitionError(`Transition from ${this.state} to ${LoadingState.Success} not allowed`); } this._data = data; this._error = undefined; this._state$.next(LoadingState.Success); } /** * Transition to error state * @param {any} error */ fail(error) { if (!this.isLoading()) { throw new IllegalStateTransitionError(`Transition from ${this.state} to ${LoadingState.Error} not allowed`); } this._data = undefined; this._error = error; this._state$.next(LoadingState.Error); } /** * Resets machine to not started */ reset() { if (this.isNotStarted()) { throw new IllegalStateTransitionError(`Transition from ${this.state} to ${LoadingState.NotStarted} not allowed`); } this._error = undefined; this._data = undefined; this._state$.next(LoadingState.NotStarted); } /** * @returns {Boolean} True if machine if loading has not been started or reset */ isNotStarted() { return this.state === LoadingState.NotStarted; } /** * @returns {Boolean} True if machine is in loading state */ isLoading() { return this.state === LoadingState.Loading; } /** * * @returns {Boolean} True if machine is in error state */ isError() { return this.state === LoadingState.Error; } /** * @returns {Boolean} True if machine is in success state */ isSuccess() { return this.state === LoadingState.Success; } /** * Factory to create a new machine in error state * @param {any} error * @returns {LoadingStateMachine<T>} The new LoadingStateMachine */ static asError(error) { const state = new LoadingStateMachine(); state.start(); state.fail(error); return state; } /** * Factory to create a new machine in success state * @param {T} data * @returns {LoadingStateMachine<T>} The new LoadingStateMachine */ static asSuccess(data) { const state = new LoadingStateMachine(); state.start(); state.succeed(data); return state; } /** * Factory to create a new machine in loading state * @param {T | undefined} data * @returns {LoadingStateMachine<T>} The new LoadingStateMachine */ static asLoading(data = undefined) { const state = new LoadingStateMachine(); state.start(); state.update(data); return state; } }