Check the playground.ide
import {Counter, CountDownState, ConterStateKeys, PartialCountDownState} from './counter' import { Subject, Observable, merge, timer, NEVER, combineLatest} from 'rxjs'; import { map, mapTo, switchMap, pluck, scan, startWith,shareReplay, distinctUntilChanged, withLatestFrom, tap} from 'rxjs/operators'; // EXERCISE DESCRIPTION ============================== /** * Use `ConterStateKeys` for property names. * Explort the counterUI API by typing `counterUI.` somewhere. ;) * * Implement all features of the counter: * 1. Start, pause the counter. Then restart the counter with 0 (+) * 2. Start it again from paused number (++) * 3. If Set to button is clicked set counter value to input value while counting (+++) * 4. Reset to initial state if reset button is clicked (+) * 5. If count up button is clicked count up, if count down button is clicked count down (+) * 6. Change interval if input tickSpeed input changes (++) * 7. Change count up if input countDiff changes (++) * 8. Take care of rendering execution and other performance optimisations as well as refactoring (+) */ // ================================================================== // = BASE OBSERVABLES ==================================================== // == SOURCE OBSERVABLES ================================================== const initialConterState: CountDownState = { isTicking: false, count: 0, countUp: true, tickSpeed: 200, countDiff:1 }; const counterUI = new Counter( document.body, { initialSetTo: initialConterState.count + 10, initialTickSpeed: initialConterState.tickSpeed, initialCountDiff: initialConterState.countDiff, } ); // === STATE OBSERVABLES ================================================== const programmaticCommandSubject = new Subject<PartialCountDownState>(); const counterCommands$ = merge( counterUI.btnStart$.pipe(mapTo({isTicking: true})), counterUI.btnPause$.pipe(mapTo({isTicking: false})), counterUI.btnSetTo$.pipe(map(n => ({count: n}))), counterUI.btnUp$.pipe(mapTo({countUp: true})), counterUI.btnDown$.pipe(mapTo({countUp: false})), counterUI.btnReset$.pipe(mapTo({...initialConterState})), counterUI.inputTickSpeed$.pipe(map ( n => ({tickSpeed: n}))), counterUI.inputCountDiff$.pipe(map ( n => ({countDiff: n}))), programmaticCommandSubject.asObservable() ); const counterState$ = counterCommands$ .pipe( startWith(initialConterState), scan((state, command) => ({ ...state, ...command })), shareReplay(1) ); // === INTERACTION OBSERVABLES ============================================ const count$ = counterState$.pipe( queryState('count') ); const isTicking$ = counterState$.pipe( queryState('isTicking') ); const intervalTick$ = isTicking$.pipe( switchMap(isTicking => isTicking ? timer(0, initialConterState.tickSpeed): NEVER) ); // = SIDE EFFECTS ========================================================= // == UI INPUTS =========================================================== const renderCountChange$ = count$ .pipe( tap((n: number) => counterUI.renderCounterValue(n)) ); // == UI OUTPUTS ========================================================== const commandFromTick$ = intervalTick$ .pipe( withLatestFrom(count$, (_, count) => count), tap((count: number) => programmaticCommandSubject.next({count: ++count})) ); // == SUBSCRIPTION ======================================================== merge( // Input side effect renderCountChange$, // Outputs side effect commandFromTick$ ) .subscribe(); // == CREATION METHODS ==================================================== function queryState (name) { return function (o$) { return o$.pipe( pluck(name), distinctUntilChanged() ) } }