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