From dea1c39b7b321270cbb94558fb8d7a8fed663975 Mon Sep 17 00:00:00 2001 From: Wouter Heirstrate Date: Mon, 1 Jul 2024 08:42:28 +0200 Subject: [PATCH] feat(dispatch-data-to-store): add function overloads and allow SSOT by using selector --- apps/store-test/src/app/app.component.ts | 29 ++-- .../src/services/courses.service.ts | 20 ++- .../dispatch-data-to-store.util.ts | 152 ++++++++++++++++-- 3 files changed, 174 insertions(+), 27 deletions(-) diff --git a/apps/store-test/src/app/app.component.ts b/apps/store-test/src/app/app.component.ts index ea13dbc3..4b171d9b 100644 --- a/apps/store-test/src/app/app.component.ts +++ b/apps/store-test/src/app/app.component.ts @@ -1,6 +1,5 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; -import { tap } from 'rxjs'; import { actions, selectors } from '../store/user.store'; import { CoursesService } from '../services/courses.service'; @@ -20,20 +19,32 @@ export class AppComponent { this.store.dispatch(actions.users.effects.add({ payload: 'serguey' })); this.store.dispatch(actions.paging.effects.set()); - this.store.select(selectors.admins.selectAll).pipe(tap(console.log)).subscribe(); + this.store.select(selectors.admins.selectAll).subscribe((result) => { + console.log('Admins: ', result); + }); + + this.courseService.state.completed$.subscribe((result) => { + console.log('Completed ', result); + }); + + this.courseService.state.coursesLoading$.subscribe((result) => { + console.log('Loading ', result); + }); + + this.courseService.state.courses$.subscribe((result) => { + console.log('Courses ', result); + }); - this.courseService.state.completed$.subscribe(console.log); - this.courseService.state.coursesLoading$.subscribe((result) => - console.log('Loading ', result) - ); - this.courseService.state.courses$.subscribe((result) => console.log('Courses ', result)); this.courseService.state.amount$.subscribe((result) => { console.log('Amount ', result); }); setTimeout(() => { this.courseService.setCompleted(); - this.courseService.dispatchCourses().subscribe(); - }, 5000); + this.courseService.dispatchCourses().subscribe((courses) => { + // Wouter: should be undefined because no selector was provided in the switchmap + console.log('Amount of courses after delay: ', courses); + }); + }, 1200); } } diff --git a/apps/store-test/src/services/courses.service.ts b/apps/store-test/src/services/courses.service.ts index 241cab96..e8671adf 100644 --- a/apps/store-test/src/services/courses.service.ts +++ b/apps/store-test/src/services/courses.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { of } from 'rxjs'; +import { Observable, of, switchMap } from 'rxjs'; import { CoursesStore, actions, selectors } from '../store/courses.store'; import { StoreService, dispatchDataToStore } from '@ngx/store'; @@ -14,7 +14,21 @@ export class CoursesService extends StoreService { this.store.dispatch(actions.completed.set({ payload: true })); } - dispatchCourses() { - return dispatchDataToStore(actions.courses, of(['hello', 'world']), this.store); + dispatchCourses(): Observable { + return dispatchDataToStore( + actions.courses, + selectors.courses, + of(['hello', 'world']), + this.store + ).pipe( + switchMap((courses) => { + return dispatchDataToStore( + actions.amount, + undefined, + of(courses.length), + this.store + ); + }) + ); } } diff --git a/libs/store/src/lib/store/utils/dispatch-data-to-store/dispatch-data-to-store.util.ts b/libs/store/src/lib/store/utils/dispatch-data-to-store/dispatch-data-to-store.util.ts index c192a3c0..7aaa74f4 100644 --- a/libs/store/src/lib/store/utils/dispatch-data-to-store/dispatch-data-to-store.util.ts +++ b/libs/store/src/lib/store/utils/dispatch-data-to-store/dispatch-data-to-store.util.ts @@ -1,21 +1,93 @@ import { Store } from '@ngrx/store'; -import { Observable, throwError } from 'rxjs'; -import { catchError, finalize, tap } from 'rxjs/operators'; +import { Observable, of, throwError } from 'rxjs'; +import { catchError, finalize, switchMap, tap } from 'rxjs/operators'; +import { + BaseStoreActions, + BaseStoreSelectors, + EntityStoreActions, + EntityStoreSelectors, +} from '../../interfaces'; + +// ------------------- +// Overloads +// ------------------- +// BASE STORE +export function dispatchDataToStore( + dispatchAction: BaseStoreActions, + selectAction: BaseStoreSelectors, + data: Observable, + store: Store, + dispatchType: 'set' +): Observable; +export function dispatchDataToStore( + dispatchAction: BaseStoreActions, + selectAction: BaseStoreSelectors, + data: Observable, + store: Store +): Observable; +// NO SELECTOR +export function dispatchDataToStore( + dispatchAction: BaseStoreActions, + selectAction: null | undefined, + data: Observable, + store: Store, + dispatchType: 'set' +): Observable; +export function dispatchDataToStore( + dispatchAction: BaseStoreActions, + selectAction: null | undefined, + data: Observable, + store: Store +): Observable; +// ENTITY STORE +export function dispatchDataToStore( + dispatchAction: EntityStoreActions, + selectAction: EntityStoreSelectors, + data: Observable, + store: Store, + dispatchType: 'set' | 'add' | 'update' +): Observable; +export function dispatchDataToStore( + dispatchAction: EntityStoreActions, + selectAction: EntityStoreSelectors, + data: Observable, + store: Store +): Observable; +// NO SELECTOR +export function dispatchDataToStore( + dispatchAction: EntityStoreActions, + selectAction: null | undefined, + data: Observable, + store: Store, + dispatchType: 'set' | 'add' | 'update' +): Observable; +export function dispatchDataToStore( + dispatchAction: EntityStoreActions, + selectAction: null | undefined, + data: Observable, + store: Store +): Observable; + +// ------------------- +// Definition +// ------------------- /** * Dispatches data to the store based on a provided action and Observable. Loading and error state will be handled by default. * * @param dispatchAction - The action we wish to use to dispatch data to the store + * @param selectAction - The selector we wish to use to select the data from the store. If not provided, `null` will be returned. * @param data - The data we wish to dispatch to the store * @param store - The store we wish to dispatch the data to * @param dispatchType - Whether we wish to set, add or update the data. By default this is `set` - * @return {*} {Observable} + * @returns {Observable<(DataType | DataType[] | null)>} - Returns the data that was dispatched to the store. The generic type is the same as the `data`. */ -export const dispatchDataToStore = ( - dispatchAction: any, - data: Observable, +export function dispatchDataToStore( + dispatchAction: BaseStoreActions | EntityStoreActions, + selectAction: BaseStoreSelectors | EntityStoreSelectors | undefined, + data: Observable, store: Store, dispatchType: 'set' | 'add' | 'update' = 'set' -): Observable => { +): Observable | null { // Iben: Set the loading state to true and the error state to false to start a new set store.dispatch(dispatchAction.loading({ payload: true })); store.dispatch(dispatchAction.error({ payload: false })); @@ -23,15 +95,65 @@ export const dispatchDataToStore = ( // Iben: Set, add or update the data according to the provided dispatch type return data.pipe( tap((payload) => { - if (dispatchType === 'set') { - store.dispatch(dispatchAction.set({ payload })); - } else if (dispatchType === 'add') { - store.dispatch(dispatchAction.add({ payload })); - } else { - store.dispatch(dispatchAction.update({ payload })); + switch (dispatchType) { + case 'set': { + return payload instanceof Array + ? store.dispatch( + (dispatchAction as EntityStoreActions).set({ + payload, + }) + ) + : store.dispatch( + (dispatchAction as BaseStoreActions).set({ + payload, + }) + ); + } + case 'add': { + if (!(payload instanceof Array)) { + return throwError( + () => + new Error( + 'NgxStore: Payload must be an array when using "add". Also make sure your storeslice is of type "EntityStoreAssets".' + ) + ); + } + + return store.dispatch( + (dispatchAction as EntityStoreActions).add({ payload }) + ); + } + case 'update': { + if (payload instanceof Array) { + return throwError( + () => + new Error( + 'NgxStore: Payload must not be an array when using "update".' + ) + ); + } + + return store.dispatch( + (dispatchAction as EntityStoreActions).update({ + payload, + }) + ); + } + } + }), + switchMap((payload) => { + if (!selectAction) { + return of(null); } + + // Wouter: Get the value recently set to the store. This is done to keep the Store as SSOT. + return store.select( + payload instanceof Array + ? (selectAction as EntityStoreSelectors).selectAll + : (selectAction as BaseStoreSelectors).select + ); }), - // Iben: Catch the error and dispatch it to the store + // Iben: Catch the error and dispatch its to the store catchError((err) => { store.dispatch(dispatchAction.error({ payload: err })); @@ -42,4 +164,4 @@ export const dispatchDataToStore = ( store.dispatch(dispatchAction.loading({ payload: false })); }) ); -}; +}