I'm using #ngrx/router-store and setting up a CustomerSerializer to manage routes in my state tree. I'm using the example in the #ngrx/router-store configuration docs. Also setup a RouterEffects class with a navigate$ effect to a specified path:
navigate$ = createEffect(():any => {
return this.actions$.pipe(
ofType(RouterActions.GO),
map((action: RouterActions.Go) => action.payload),
tap(({ path, query: queryParams, extras }) => {
this.router.navigate(path, { queryParams, ...extras });
})
)
}
);
When I dispatch a "GO" action with a payload, the Angular router navigates to the specified path, but I get this error in the console:
ERROR Error: Effect "RouterEffects.navigate$" dispatched an invalid action: {"path":["/admin"]}
I don't understand where the secondary action is being dispatched.
The problem is map((action: RouterActions.Go) => action.payload),
An effect always dispatches the next value back to the store.
This should be an action, but in this case it's the action's payload, resulting in the error.
To fix this you need to create non-dispatching effect.
navigate$ = createEffect(():any => {
return this.actions$.pipe(
ofType(RouterActions.GO),
map((action: RouterActions.Go) => action.payload),
tap(({ path, query: queryParams, extras }) => {
this.router.navigate(path, { queryParams, ...extras });
})
)
}
// add this 👇
, {dispatch: false}
);
Related
Component.ts
ngOnInit() {
this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);
}
service.ts
constructor(private http: HttpClient) { }
public getEmp_ServiceFun(): Observable<Employee[]> {
return this.http.get<Employee[]>(this.serverUrl + 'employees')
.pipe(
catchError(this.handleError)
);
}
it seems a bit strange to me to run the ngoninit method again since it is meant to run only once. I would wrap the employeeservice method in an observable interval. dont forget to unsubscribe though. otherwise it would keep calling the getEmp_ServiceFun until the whole app closes
ngOnInit() {
interval(1000).pipe(
map(() => {this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);})}
this would replace the whole array instead of just adding to it though. I would take a second look at the getEmp_ServiceFun so that you can ask only for new data and not all data and then push it to the array.
edit: even better would be to not subscribe in the the map but move it to after the pipe. you might need to use a switchMap
I used setTimeout() to refresh a component, it is working fine, but now I just need to check, is it good practice or not?
ngOnInit() {
this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);
//refresh this component
this.timeout = setTimeout(() => { this.ngOnInit() }, 1000 * 1)
}
I am trying to modify an effect I have made into letting me start and stop multiple firestore queries by using two actions. Currently the effect allows me to start and stop a single firestore query by listening for two separate actions in the effect. I simply use a switchMap to switch into an empty observable when there is a stop action. This works just fine.
#Effect()
startStopQuery$ = this.actions$.pipe(
ofType(
ActionTypes.START,
ActionTypes.STOP
),
switchMap(action => {
if (action.type === ActionTypes.STOP) {
return of([]);
} else {
return this.afs.collection('collection', ref => {
return ref.where('field', '==', 'x');
}).stateChanges();
}
}),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
What I actually want to do is to have multiple queries ongoing that I can start and stop with those same two actions, but where it depends on the action payload. When I modified it everytime I performed a new query the last one stops working. I think it is because the switchMap operator switches away from my last query observable. This is the best I have come up with:
#Effect()
startStopQueryById$ = this.actions$.pipe(
ofType(
ActionTypes.START_BY_ID,
ActionTypes.STOP_BY_ID
),
switchMap(action => {
if (action.type === ActionTypes.STOP_BY_ID) {
return of([]);
} else {
return this.afs.collection('collection', ref => {
return ref.where('field', '==', action.id);
}).stateChanges();
}
}),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
As I said, I think the issue is the switchMap operator. But that is also what I depended on to make the "stop" work in the first place. I cant seem to come up with another solution as I am not very well versed in the style yet.
Any help would be greatly appreciated!
I came up with a solution. I make an object that maps ID's to the firestore statechanges observables. On the start action I make the listener and adds it to the object. I make sure that it automatically unsubscribe by piping takeUntil with the corresponding stop action. It returns a merge of all the observables in the object and I silply do as before. I also have a seperate effect triggered by the stop action to remove the observable from the object. It looks like so:
queriesById: {[id: string]: Observable<DocumentChangeAction<Element>[]>} = {};
#Effect()
startQuery$ = this.actions$.pipe(
ofType(ActionTypes.START_BY_ID),
switchMap(action => {
this.queriesByPlay[action.pid] = this.afs.collection<Element>('requests', ref => {
return ref.where('field', '==', action.id);
}).stateChanges().pipe(
takeUntil(
this.actions$.pipe(
ofType(ActionTypes.STOP_BY_ID),
filter(cancelAction => action.id === cancelAction.id),
)
)
);
return merge(
Object.values(this.queriesByPlay)
);
}),
mergeMap(actions => actions),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
Effect({dispatch: false})
stopQuery$ = this.actions$.pipe(
ofType(ActionTypes.STOP_BY_ID),
map(action => delete this.queriesByPlay[action.id]),
);
This seems to work and have no problems except for being convoluted hard to understand.
I am currently using ngrx in my app to manage state. I was looking at switching to ngrx-data for the simple CRUD operations and ran across my first question. This is my current regular ngrx effect and I want to know how to reproduce it with ngrx-data (create new action based on http response):
#Effect()
insertUser$: Observable<Action> = this.action$.pipe(
ofType(UsersActionTypes.UserInsert),
map((action: UserInsert) => action.payload),
mergeMap(payload =>
this.UserService.insert(<IUserPersistRequest>{
user: payload.User,
refreshToken: payload.loggedInUser.RefreshToken
}).pipe(
map((response: IUserPersistResponse) => {
return !response.accessToken
? new UserPersistSuccessLoginFailure()
: new UserInsertSuccess({
response: response,
User: payload.User
});
}),
catchError((error: string) => {
return of(new UserInsertFailure('UserInsert Failed'));
})
)
)
);
Any advice?
Usually I use redux-saga, but currently I need redux-thunk. I'm using ducks for modular structure and now for example I have two ducks: auth and user with async actions below:
auth-duck.js
register(credentials) {
return dispatch => {
dispatch(actions.registerRequest());
return service.requestRegister(credentials)
.then((response) => {
dispatch(actions.registerSuccess(...));
// Here I need to dispatch some action from user-duck.js
})
.catch(() => dispatch(actions.registerError(...)))
}
}
user-duck.js
fetchUser() {
return dispatch => {...}
}
I really don't know how to not mess these two modules and dispatch fetchUser after successful register.
I could return register result (e.g. token or something else) to container from here it was dispatched and then using chaining dispatch fetchUser.
AuthContainer.js
_onSubmit() {
this.props.register().then(() => this.props.fetchUser);
}
But I don't know is it the best way to manage such operations with redux-thunk?
There is no rule thunks can only make one HTTP request. If you need to fetch the user after login, then fetch it.
const login = credentials => dispatch => {
fetchLogin(credentials).then(() => {
dispatch({ type: "LoginSuccess" })
return fetchUser()
}).then(() => {
dispatch({ type: "UserFetched" })
})
}
If you want to reuse the user fetch action, then dispatch a thunk from a thunk.
const fetchCurrentUser = login => dispatch => {
return fetchUser(login.userId).then(user => {
dispatch({ type: "UserLoad" })
return user
})
}
const login = credentials => dispatch => {
return fetchLogin(credentials).then(login => {
dispatch({ type: "LoginSuccess" })
return dispatch(fetchCurrentUser(login))
}
}
In one of my apps, I call 7 action thunks after successful login.
After a long search I found two options how to share the logic from separate domains.
The first one is to use mapDispatchToProps (Thanks #DonovanM), just like this:
function mapDispatchToProps(dispatch) {
return {
login: (credentials) => {
return dispatch(authActions.login(credentials)).then(
() => dispatch(userActions.fetchUser())
);
}
}
}
login function returns Promise thats why we can chain it to another one.
And the second possible option:
Use something like a "bridge" file in our case it is app-sagas.js
app-duck.js
import {actions as authActions} from './auth-duck.js';
import {actions as userActions} from './user-duck.js';
export function doLogin(credentials) {
return dispatch => {
return dispatch(authAction.login(credentials)).then(
() => dispatch(userActions.fetchUser())
);
}
}
In the second case we can place all logic into ducks and avoid spreading the logic within containers. But I guess it is possible to combine both methods.
I'm dispatching an ADD_SOURCE action from my component that when it success dispatches another ADD_SOURCE_SUCCESS:
this.store$
.select(fromRoot.getUserState)
.filter(user => user.id != null && user.logged)
.takeUntil(this.componentDestroyed$)
.do(user => this.store$.dispatch({type: 'ADD_SOURCE', payload: user.username}))
.subscribe();
This is the effect that returns the ADD_SOURCE_SUCCESS according to a net call:
#Effect({ dispatch: true })
addSource$: Observable<Action> = this.actions$
.ofType('ADD_SOURCE')
.switchMap(
(action: Action) =>
this.userService.addCard(action.payload.username, action.payload.token)
.map((card: CardDTO) => {
return <Action>{
type: 'ADD_SOURCE_SUCCESS',
payload: <ICard>{ ... }
};
})
.catch(_ => {
return Observable.of(<Action>{ type: 'ADD_SOURCE_FAILED', payload: { }});
}));
So, then a new ADD_SOURCE_SUCCESS is dispatched on my reducer:
private static saveSourceSuccess(sourcesRdx, type, payload) {
return <ISourceRedux>{
ids: [ ...sourcesRdx.ids, payload.id ],
entities: Object.assign({}, sourcesRdx.entities, {[payload.id]: payload}),
selectedIds: sourcesRdx.selectedIds,
editingSource: null
};
}
Nevertheless, I don't quite figure out how to say on my component that the operation has been success and do one thing or another one...
Any ideas?
in your reducer, you should also handle the success & fail errors, set the store with an error message or whatever you need as data, and make a selector on it. Then the ui should subscribe to this new selector and you ll be notified
check this chapter
selectors