feat(nx): infer state type, add optional action type parameter
This commit is contained in:
parent
7219c8af71
commit
6fc7145602
@ -92,7 +92,7 @@ describe('DataPersistence', () => {
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodo = this.s.navigation(TodoComponent, {
|
||||
run: (a: ActivatedRouteSnapshot, state: TodosState) => {
|
||||
run: (a, state) => {
|
||||
return {
|
||||
type: 'TODO_LOADED',
|
||||
payload: { id: a.params['id'], user: state.user }
|
||||
@ -100,7 +100,7 @@ describe('DataPersistence', () => {
|
||||
},
|
||||
onError: () => null
|
||||
});
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -131,7 +131,7 @@ describe('DataPersistence', () => {
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodo = this.s.navigation(TodoComponent, {
|
||||
run: (a: ActivatedRouteSnapshot, state: TodosState) => {
|
||||
run: (a, state) => {
|
||||
if (a.params['id'] === '123') {
|
||||
throw new Error('boom');
|
||||
} else {
|
||||
@ -143,7 +143,7 @@ describe('DataPersistence', () => {
|
||||
},
|
||||
onError: (a, e) => ({ type: 'ERROR', payload: { error: e } })
|
||||
});
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -183,7 +183,7 @@ describe('DataPersistence', () => {
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodo = this.s.navigation(TodoComponent, {
|
||||
run: (a: ActivatedRouteSnapshot, state: TodosState) => {
|
||||
run: (a, state) => {
|
||||
if (a.params['id'] === '123') {
|
||||
return _throw('boom');
|
||||
} else {
|
||||
@ -195,7 +195,7 @@ describe('DataPersistence', () => {
|
||||
},
|
||||
onError: (a, e) => ({ type: 'ERROR', payload: { error: e } })
|
||||
});
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -236,11 +236,15 @@ describe('DataPersistence', () => {
|
||||
});
|
||||
|
||||
describe('no id', () => {
|
||||
type GetTodos = {
|
||||
type: 'GET_TODOS';
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodos = this.s.fetch('GET_TODOS', {
|
||||
run: (a: any, state: TodosState) => {
|
||||
loadTodos = this.s.fetch<GetTodos>('GET_TODOS', {
|
||||
run: (a, state) => {
|
||||
// we need to introduce the delay to "enable" switchMap
|
||||
return of({
|
||||
type: 'TODOS',
|
||||
@ -248,10 +252,10 @@ describe('DataPersistence', () => {
|
||||
}).delay(1);
|
||||
},
|
||||
|
||||
onError: (a: UpdateTodo, e: any) => null
|
||||
onError: (a, e: any) => null
|
||||
});
|
||||
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
function userReducer() {
|
||||
@ -281,16 +285,21 @@ describe('DataPersistence', () => {
|
||||
});
|
||||
|
||||
describe('id', () => {
|
||||
type GetTodo = {
|
||||
type: 'GET_TODO';
|
||||
payload: { id: string };
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodo = this.s.fetch('GET_TODO', {
|
||||
id: (a: any, state: TodosState) => a.payload.id,
|
||||
run: (a: any, state: TodosState) => of({ type: 'TODO', payload: a.payload }).delay(1),
|
||||
onError: (a: UpdateTodo, e: any) => null
|
||||
loadTodo = this.s.fetch<GetTodo>('GET_TODO', {
|
||||
id: (a, state) => a.payload.id,
|
||||
run: (a, state) => of({ type: 'TODO', payload: a.payload }).delay(1),
|
||||
onError: (a, e: any) => null
|
||||
});
|
||||
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
function userReducer() {
|
||||
@ -333,15 +342,15 @@ describe('DataPersistence', () => {
|
||||
@Injectable()
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
|
||||
run: (a: UpdateTodo, state: TodosState) => ({
|
||||
loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
||||
run: (a, state) => ({
|
||||
type: 'TODO_UPDATED',
|
||||
payload: { user: state.user, newTitle: a.payload.newTitle }
|
||||
}),
|
||||
onError: (a: UpdateTodo, e: any) => null
|
||||
onError: (a, e: any) => null
|
||||
});
|
||||
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
function userReducer() {
|
||||
@ -379,18 +388,18 @@ describe('DataPersistence', () => {
|
||||
@Injectable()
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
|
||||
run: (a: UpdateTodo, state: TodosState) => {
|
||||
loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
||||
run: (a, state) => {
|
||||
throw new Error('boom');
|
||||
},
|
||||
|
||||
onError: (a: UpdateTodo, e: any) => ({
|
||||
onError: (a, e: any) => ({
|
||||
type: 'ERROR',
|
||||
payload: { error: e }
|
||||
})
|
||||
});
|
||||
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
function userReducer() {
|
||||
@ -426,18 +435,18 @@ describe('DataPersistence', () => {
|
||||
@Injectable()
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
|
||||
run: (a: UpdateTodo, state: TodosState) => {
|
||||
loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
||||
run: (a, state) => {
|
||||
return _throw('boom');
|
||||
},
|
||||
|
||||
onError: (a: UpdateTodo, e: any) => ({
|
||||
onError: (a, e: any) => ({
|
||||
type: 'ERROR',
|
||||
payload: { error: e }
|
||||
})
|
||||
});
|
||||
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
function userReducer() {
|
||||
@ -479,18 +488,18 @@ describe('DataPersistence', () => {
|
||||
@Injectable()
|
||||
class TodoEffects {
|
||||
@Effect()
|
||||
loadTodo = this.s.optimisticUpdate('UPDATE_TODO', {
|
||||
run: (a: UpdateTodo, state: TodosState) => {
|
||||
loadTodo = this.s.optimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
||||
run: (a, state) => {
|
||||
throw new Error('boom');
|
||||
},
|
||||
|
||||
undoAction: (a: UpdateTodo, e: any) => ({
|
||||
undoAction: (a, e: any) => ({
|
||||
type: 'UNDO_UPDATE_TODO',
|
||||
payload: a.payload
|
||||
})
|
||||
});
|
||||
|
||||
constructor(private s: DataPersistence<any>) {}
|
||||
constructor(private s: DataPersistence<TodosState>) {}
|
||||
}
|
||||
|
||||
function userReducer() {
|
||||
|
||||
@ -17,32 +17,32 @@ import { withLatestFrom } from 'rxjs/operator/withLatestFrom';
|
||||
/**
|
||||
* See {@link DataPersistence.pessimisticUpdate} for more information.
|
||||
*/
|
||||
export interface PessimisticUpdateOpts {
|
||||
run(a: Action, state?: any): Observable<Action> | Action | void;
|
||||
onError(a: Action, e: any): Observable<any> | any;
|
||||
export interface PessimisticUpdateOpts<T, A> {
|
||||
run(a: A, state?: T): Observable<Action> | Action | void;
|
||||
onError(a: A, e: any): Observable<any> | any;
|
||||
}
|
||||
/**
|
||||
* See {@link DataPersistence.pessimisticUpdate} for more information.
|
||||
*/
|
||||
export interface OptimisticUpdateOpts {
|
||||
run(a: Action, state?: any): Observable<any> | any;
|
||||
undoAction(a: Action, e: any): Observable<Action> | Action;
|
||||
export interface OptimisticUpdateOpts<T, A> {
|
||||
run(a: A, state?: T): Observable<Action> | Action | void;
|
||||
undoAction(a: A, e: any): Observable<Action> | Action;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link DataPersistence.navigation} for more information.
|
||||
*/
|
||||
export interface FetchOpts {
|
||||
id?(a: Action, state?: any): any;
|
||||
run(a: Action, state?: any): Observable<Action> | Action | void;
|
||||
onError?(a: Action, e: any): Observable<any> | any;
|
||||
export interface FetchOpts<T, A> {
|
||||
id?(a: A, state?: T): any;
|
||||
run(a: A, state?: T): Observable<Action> | Action | void;
|
||||
onError?(a: A, e: any): Observable<any> | any;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link DataPersistence.navigation} for more information.
|
||||
*/
|
||||
export interface HandleNavigationOpts {
|
||||
run(a: ActivatedRouteSnapshot, state?: any): Observable<Action> | Action | void;
|
||||
export interface HandleNavigationOpts<T> {
|
||||
run(a: ActivatedRouteSnapshot, state?: T): Observable<Action> | Action | void;
|
||||
onError?(a: ActivatedRouteSnapshot, e: any): Observable<any> | any;
|
||||
}
|
||||
|
||||
@ -69,9 +69,9 @@ export class DataPersistence<T> {
|
||||
* ```typescript
|
||||
* @Injectable()
|
||||
* class TodoEffects {
|
||||
* @Effect() updateTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
|
||||
* @Effect() updateTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
||||
* // provides an action and the current state of the store
|
||||
* run(a: UpdateTodo, state: TodosState) {
|
||||
* run(a, state) {
|
||||
* // update the backend first, and then dispatch an action that will
|
||||
* // update the client side
|
||||
* return this.backend(state.user, a.payload).map(updated => ({
|
||||
@ -80,7 +80,7 @@ export class DataPersistence<T> {
|
||||
* }));
|
||||
* },
|
||||
*
|
||||
* onError(a: UpdateTodo, e: any) {
|
||||
* onError(a, e: any) {
|
||||
* // we don't need to undo the changes on the client side.
|
||||
* // we can dispatch an error, or simply log the error here and return `null`
|
||||
* return null;
|
||||
@ -91,7 +91,7 @@ export class DataPersistence<T> {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pessimisticUpdate(actionType: string, opts: PessimisticUpdateOpts): Observable<any> {
|
||||
pessimisticUpdate<A = Action>(actionType: string, opts: PessimisticUpdateOpts<T, A>): Observable<any> {
|
||||
const nav = this.actions.ofType(actionType);
|
||||
const pairs = withLatestFrom.call(nav, this.store);
|
||||
return concatMap.call(pairs, this.runWithErrorHandling(opts.run, opts.onError));
|
||||
@ -114,13 +114,13 @@ export class DataPersistence<T> {
|
||||
* ```typescript
|
||||
* @Injectable()
|
||||
* class TodoEffects {
|
||||
* @Effect() updateTodo = this.s.optimisticUpdate('UPDATE_TODO', {
|
||||
* @Effect() updateTodo = this.s.optimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
||||
* // provides an action and the current state of the store
|
||||
* run: (a: UpdateTodo, state: TodosState) => {
|
||||
* run: (a, state) => {
|
||||
* return this.backend(state.user, a.payload);
|
||||
* },
|
||||
*
|
||||
* undoAction: (a: UpdateTodo, e: any) => {
|
||||
* undoAction: (a, e: any) => {
|
||||
* // dispatch an undo action to undo the changes in the client state
|
||||
* return ({
|
||||
* type: 'UNDO_UPDATE_TODO',
|
||||
@ -133,7 +133,7 @@ export class DataPersistence<T> {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
optimisticUpdate(actionType: string, opts: OptimisticUpdateOpts): Observable<any> {
|
||||
optimisticUpdate<A = Action>(actionType: string, opts: OptimisticUpdateOpts<T, A>): Observable<any> {
|
||||
const nav = this.actions.ofType(actionType);
|
||||
const pairs = withLatestFrom.call(nav, this.store);
|
||||
return concatMap.call(pairs, this.runWithErrorHandling(opts.run, opts.undoAction));
|
||||
@ -153,16 +153,16 @@ export class DataPersistence<T> {
|
||||
* ```typescript
|
||||
* @Injectable()
|
||||
* class TodoEffects {
|
||||
* @Effect() loadTodos = this.s.fetch('GET_TODOS', {
|
||||
* @Effect() loadTodos = this.s.fetch<GetTodos>('GET_TODOS', {
|
||||
* // provides an action and the current state of the store
|
||||
* run: (a: GetTodos, state: TodosState) => {
|
||||
* run: (a, state) => {
|
||||
* return this.backend(state.user, a.payload).map(r => ({
|
||||
* type: 'TODOS',
|
||||
* payload: r
|
||||
* });
|
||||
* },
|
||||
*
|
||||
* onError: (a: GetTodos, e: any) => {
|
||||
* onError: (a, e: any) => {
|
||||
* // dispatch an undo action to undo the changes in the client state
|
||||
* }
|
||||
* });
|
||||
@ -178,20 +178,20 @@ export class DataPersistence<T> {
|
||||
* ```typescript
|
||||
* @Injectable()
|
||||
* class TodoEffects {
|
||||
* @Effect() loadTodo = this.s.fetch('GET_TODO', {
|
||||
* id: (a: GetTodo, state: TodosState) => {
|
||||
* @Effect() loadTodo = this.s.fetch<GetTodo>('GET_TODO', {
|
||||
* id: (a, state) => {
|
||||
* return a.payload.id;
|
||||
* }
|
||||
*
|
||||
* // provides an action and the current state of the store
|
||||
* run: (a: GetTodo, state: TodosState) => {
|
||||
* run: (a, state) => {
|
||||
* return this.backend(state.user, a.payload).map(r => ({
|
||||
* type: 'TODO',
|
||||
* payload: r
|
||||
* });
|
||||
* },
|
||||
*
|
||||
* onError: (a: GetTodo, e: any) => {
|
||||
* onError: (a, e: any) => {
|
||||
* // dispatch an undo action to undo the changes in the client state
|
||||
* return null;
|
||||
* }
|
||||
@ -206,7 +206,7 @@ export class DataPersistence<T> {
|
||||
* In addition, if DataPersistence notices that there are multiple requests for Todo 1 scheduled,
|
||||
* it will only run the last one.
|
||||
*/
|
||||
fetch(actionType: string, opts: FetchOpts): Observable<any> {
|
||||
fetch<A = Action>(actionType: string, opts: FetchOpts<T, A>): Observable<any> {
|
||||
const nav = this.actions.ofType(actionType);
|
||||
const allPairs = withLatestFrom.call(nav, this.store);
|
||||
|
||||
@ -237,13 +237,13 @@ export class DataPersistence<T> {
|
||||
* @Injectable()
|
||||
* class TodoEffects {
|
||||
* @Effect() loadTodo = this.s.navigation(TodoComponent, {
|
||||
* run: (a: ActivatedRouteSnapshot, state: TodosState) => {
|
||||
* run: (a, state) => {
|
||||
* return this.backend.fetchTodo(a.params['id']).map(todo => ({
|
||||
* type: 'TODO_LOADED',
|
||||
* payload: todo
|
||||
* }));
|
||||
* },
|
||||
* onError: (a: ActivatedRouteSnapshot, e: any) => {
|
||||
* onError: (a, e: any) => {
|
||||
* // we can log and error here and return null
|
||||
* // we can also navigate back
|
||||
* return null;
|
||||
@ -254,7 +254,7 @@ export class DataPersistence<T> {
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
navigation(component: Type<any>, opts: HandleNavigationOpts): Observable<any> {
|
||||
navigation(component: Type<any>, opts: HandleNavigationOpts<T>): Observable<any> {
|
||||
const nav = filter.call(
|
||||
map.call(this.actions.ofType(ROUTER_NAVIGATION), (a: RouterNavigationAction<RouterStateSnapshot>) =>
|
||||
findSnapshot(component, a.payload.routerState.root)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user