Skip to content

Commit

Permalink
feat(dispatch-data-to-store): add function overloads and allow SSOT b…
Browse files Browse the repository at this point in the history
…y using selector
  • Loading branch information
Wouter Heirstrate committed Jul 1, 2024
1 parent d2e69c1 commit 089a236
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 27 deletions.
29 changes: 20 additions & 9 deletions apps/store-test/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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);
}
}
20 changes: 17 additions & 3 deletions apps/store-test/src/services/courses.service.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -14,7 +14,21 @@ export class CoursesService extends StoreService<CoursesStore> {
this.store.dispatch(actions.completed.set({ payload: true }));
}

dispatchCourses() {
return dispatchDataToStore(actions.courses, of(['hello', 'world']), this.store);
dispatchCourses(): Observable<number> {
return dispatchDataToStore(
actions.courses,
selectors.courses,
of(['hello', 'world']),
this.store
).pipe(
switchMap((courses) => {
return dispatchDataToStore(
actions.amount,
undefined,
of(courses.length),
this.store
);
})
);
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,159 @@
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<DataType = any>(
dispatchAction: BaseStoreActions<DataType>,
selectAction: BaseStoreSelectors<DataType>,
data: Observable<DataType>,
store: Store,
dispatchType: 'set'
): Observable<DataType>;
export function dispatchDataToStore<DataType = any>(
dispatchAction: BaseStoreActions<DataType>,
selectAction: BaseStoreSelectors<DataType>,
data: Observable<DataType>,
store: Store
): Observable<DataType>;
// NO SELECTOR
export function dispatchDataToStore<DataType = any>(
dispatchAction: BaseStoreActions<DataType>,
selectAction: null | undefined,
data: Observable<DataType>,
store: Store,
dispatchType: 'set'
): Observable<null>;
export function dispatchDataToStore<DataType = any>(
dispatchAction: BaseStoreActions<DataType>,
selectAction: null | undefined,
data: Observable<DataType>,
store: Store
): Observable<null>;
// ENTITY STORE
export function dispatchDataToStore<DataType = any>(
dispatchAction: EntityStoreActions<DataType>,
selectAction: EntityStoreSelectors<DataType>,
data: Observable<DataType[]>,
store: Store,
dispatchType: 'set' | 'add' | 'update'
): Observable<DataType[]>;
export function dispatchDataToStore<DataType = any>(
dispatchAction: EntityStoreActions<DataType>,
selectAction: EntityStoreSelectors<DataType>,
data: Observable<DataType[]>,
store: Store
): Observable<DataType[]>;
// NO SELECTOR
export function dispatchDataToStore<DataType = any>(
dispatchAction: EntityStoreActions<DataType>,
selectAction: null | undefined,
data: Observable<DataType[]>,
store: Store,
dispatchType: 'set' | 'add' | 'update'
): Observable<null>;
export function dispatchDataToStore<DataType = any>(
dispatchAction: EntityStoreActions<DataType>,
selectAction: null | undefined,
data: Observable<DataType[]>,
store: Store
): Observable<null>;

// -------------------
// 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<DataType>}
* @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 = <DataType>(
dispatchAction: any,
data: Observable<DataType>,
export function dispatchDataToStore<DataType = any>(
dispatchAction: BaseStoreActions<DataType> | EntityStoreActions<DataType>,
selectAction: BaseStoreSelectors<DataType> | EntityStoreSelectors<DataType> | undefined,
data: Observable<DataType | DataType[]>,
store: Store,
dispatchType: 'set' | 'add' | 'update' = 'set'
): Observable<DataType> => {
): Observable<DataType | DataType[]> | 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 }));

// 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<DataType>).set({
payload,
})
)
: store.dispatch(
(dispatchAction as BaseStoreActions<DataType>).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<DataType>).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<DataType>).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 }));

Expand All @@ -42,4 +164,4 @@ export const dispatchDataToStore = <DataType>(
store.dispatch(dispatchAction.loading({ payload: false }));
})
);
};
}

0 comments on commit 089a236

Please sign in to comment.