When handling in NgRX Effect, the effect won't work anymore - ngrx

For the life of me, I can't figure out why once and error is thrown and intercepted, the effect will not work anymore
#Effect()
register$ = createEffect(() => {
return this.actions$.pipe(
ofType(RegisterAction),
map(action => action.registrationInfo),
mergeMap(registrationInfo => {
return this.authService.createUser(registrationInfo.email, registrationInfo.password,
registrationInfo.lastname, registrationInfo.firstname);
}),
map(credentialInfo => ProfileInitAction({credentialInfo})),
catchError(error => [ProfileInitErrorAction(error)]),
);
});

You know why? Because this is the normal workflow in RXJS. So an observable emits values at different times. When an error occurs, then the observable chain (or pipe or subscription, or what you want) breaks. You have a service call. This service call can fail, right? But you do this service call in a child observable chain. So you can handle the error in this child's observable chain, and this will not break your main observable chain. In one word, do the catchError in the mergeMap.
For example:
import { Injectable } from '#angular/core';
import { Actions, createEffect, ofType } from '#ngrx/effects';
import { EMPTY } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { MoviesService } from './movies.service';
#Injectable()
export class MovieEffects {
loadMovies$ = createEffect(() => this.actions$.pipe(
ofType('[Movies Page] Load Movies'),
mergeMap(() => this.moviesService.getAll() //< --- starting point of the child observable chain
.pipe(
map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),
catchError(() => EMPTY) // <--- this is runs in the child observable chain
))
) // <--- end of the child observable chain
); // <--- end of the main observable chain
constructor(
private actions$: Actions,
private moviesService: MoviesService
) {}
}

Adding caught$ in the catchError seems to fix this.
register$ = createEffect(() => {
return this.actions$.pipe(
ofType(RegisterAction),
map(action => action.registrationInfo),
mergeMap(registrationInfo => {
return this.authService.createUser(registrationInfo.email, registrationInfo.password,
registrationInfo.lastname, registrationInfo.firstname);
}),
map(credentialInfo => ProfileInitAction({credentialInfo})),
catchError((err, caught$) => {
notify(this.translate.instant('pages.auth.register.notify.error' + ': ' + err['message']), 'error');
return caught$;
}),
);
});

Related

how to combine 2 selectors in ngrx effect

here's the scenario. i have this 2 different selectors which compose of different properties. what i want to happen is to combine them together which will be later on to be used as a parameters:
here's the codes:
#Injectable()
export class AuthenticationEffect {
getJwtToken$ = createEffect(() => {
return this.actions$.pipe(
ofType(TransactionIDActionTypes.LOAD_SUCCESS),
concatMap(action => of(action).pipe(withLatestFrom(
this.store.select(getTransactionIDSelector),
this.store.select(getAuthPayload)
))),
mergeMap(([, transactionId, authPayload]) => {
return this.authenticationService.getJwtToken(this.AuthenticateRequest(transactionId)).pipe(
map(response => {
return LoadSessionSuccess(response);
}),
catchError(error => of(LoadSessionError(error)))
);
})
);
});
constructor(private actions$: Actions, private store: Store<any>, private authenticationService: AuthenticateService) {}
AuthenticateRequest(transactionId): AuthenticateInterface {
return {
transactionId,
sourceIdentifier,
residentType,
organizationCode,
profile,
kycId,
identificationNumber,
mobileIdentifier,
nationality,
dateAcceptedTermsOfUse,
};
}
}
i need to execute this two selectors
this.store.select(getTransactionIDSelector),
this.store.select(getAuthPayload)
at the same time, is this possible? and how do i get the state value for each selectors?
You can use concatLatestFrom for this (instead of concatMap in combination with withLatestFrom).
...
concatLatestFrom(() => [
this.store.select(getTransactionIDSelector),
this.store.select(getAuthPayload)
]),
...

ngrx twice reducer call during effect

I am developing using an angular app using ngrx. I have defined the convention below to implement a loading indicator:
Initially state of each entity is set to null
Make it an empty object on effect starts
Fill it with fetched data on effect done
Now one of my effects will be this:
#Effect()
LoginUser$ = this._actions$.pipe(
ofType<LoginUser>(EUserActions.LoginUser),
switchMap((params) => { new LoginUserSuccess(<IUser>{}); return of(params); }), // for loading indicator to be shown
switchMap((params) => this._userService.loginUser(params.payload)),
switchMap((currentUser: IUser) => of(new LoginUserSuccess(currentUser)))
)
but the reducer call in the first switchMap does not get occur. What is the problem.
An effect is a stream, only the last Action in the stream will be dispatched.
For your case you can listen on LoginUser in your reducer and empty your state.
I have finally solved my problem some other way. I now am dispatching another action, inside the primary action to update the state. For example this is how I have done it:
user.service.ts
export class UserService {
constructor(private _store: Store<IAppState>) { }
loginUser(model): void {
this._store.dispatch(new AddBusy(EUserActions.LoginUser));
this._store.dispatch(new LoginUser(model));
}
getAllUsers(): void {
this._store.dispatch(new AddBusy(EUserActions.GetAllUsers));
this._store.dispatch(new GetAllUsers());
}
}
user.actions.ts
export class UserEffects {
#Effect()
LoginUser$ = this._actions$.pipe(
ofType<LoginUser>(EUserActions.LoginUser),
switchMap((params) => this._userLogic.loginUser(params.payload)),
switchMap((currentUser: IUser) => { this._store.dispatch(new RemoveBusy(EUserActions.LoginUser)); return of(currentUser); }),
switchMap((currentUser: IUser) => of(new LoginUserSuccess(currentUser)))
)
#Effect()
getAllUsers$ = this._actions$.pipe(
ofType<GetAllUsers>(EUserActions.GetAllUsers),
switchMap(() => this._userLogic.getAllUsers()),
switchMap((users: IUser[]) => { this._store.dispatch(new RemoveBusy(EUserActions.GetAllUsers)); return of(users); }),
switchMap((users: IUser[]) => of(new GetAllUsersSuccess(users)))
)
constructor(
private _userLogic: UserLogic,
private _actions$: Actions,
private _store: Store<IAppState>,
) { }
}
This solved my problem nice.

Redux Observable ( Epic ) - concat two promises before returning action

I'm stuck trying to accomplish the following. In my React app, I am using redux-observable Epics.
I have two promises, one which needs to wait for the second, before it fires.
import { map, mergeMap, catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { fromPromise } from 'rxjs/observable/fromPromise';
.....
const promise1 = Auth.getCredentials().then( credentials => {
return credentials
}
const promise2 = ( credentials ) => {
return doQuery(credentials, someData).then(function(data) {
// return success
}).catch(function(err) {
// reject error
});
}
So promise 2 needs the credentials from promise1, I am having a hard time knowing how to use observable/fromPromise etc to 'chaing' these items together so that the result of ends up either in the 'map' or 'catchError' result
In my Epic, i have something like this:
const searchEpic: Epic<RootAction, RootState> =
(action$, store) => action$.ofType(DO_QUERY)
.mergeMap(({payload}) => {
???????? - this is where Im stuck
const $stream = RESULT_OF_PROMISES.pipe(
map((response) => {
return actionCreators.success(..)
},
catchError(e => {
return of(actionCreators.failure(..));
}
)));
return $stream
});
Thank you!

Redux observable cancel next operator execution?

I am using redux-observable with redux for async actions. Inside epic's map operator i am doing some pre processing because its the central place.
My app calling same action from multiple container components with different values.
So basically i have to cancel my ajax request/next operator execution if deepEqual(oldAtts, newAtts) is true
code -
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(function(action) {
let oldAtts = store.getState().catalog.filterAtts
let newAtts = Object.assign({}, oldAtts, action.atts)
if (deepEqual(oldAtts, newAtts)) {
// Don't do new ajax request
}
const searchString = queryString.stringify(newAtts, {
arrayFormat: 'bracket'
})
// Push new state
pushState(newAtts)
// Return new `action` object with new key `searchString` to call API
return Object.assign({}, action, {
searchString
})
})
.mergeMap(action =>
ajax.get(`/products?${action.searchString}`)
.map(response => doFetchProductsFulfilled(response))
.catch(error => Observable.of({
type: FETCH_PRODUCTS_FAILURE,
payload: error.xhr.response,
error: true
}))
.takeUntil(action$.ofType(FETCH_PRODUCTS_CANCEL))
);
}
Not sure whether its right way to do it from epic.
Thanks in advance.
You can do this:
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(action => ({
oldAtts: store.getState().catalog.filterAtts,
newAtts: Object.assign({}, oldAtts, action.atts)
}))
.filter(({ oldAtts, newAtts }) => !deepEqual(oldAtts, newAtts))
.do(({ newAtts }) => pushState(newAtts))
.map(({ newAtts }) => queryString.stringify(newAtts, {
arrayFormat: 'bracket'
}))
.mergeMap(searchString => ...);
}
But most likely you do not need to save the atts to state to do the comparison:
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(action => action.atts)
.distinctUntilChanged(deepEqual)
.map(atts => queryString.stringify(atts, { arrayFormat: 'bracket' }))
.mergeMap(searchString => ...);
}

Test Angular2 service with mock backend

First: I'm aware that Angular2 is in alpha and changing frequently.
I'm working with Angular2. There is an injectable service with http dependency that I'd like to test using a mock backend. The service works when the app starts but I'm having no luck writing the test and getting the mock backend to respond. Any insight, is there something obvious in the test setup or implementation that I'm missing?
service/core.ts:
import { Injectable } from 'angular2/angular2';
import { Http } from 'angular2/http';
#Injectable()
export class CoreService {
constructor(public http:Http) {}
getStatus() {
return this.http.get('/api/status')
.toRx()
.map(res => res.json());
}
}
service/core_spec.ts:
import {
AsyncTestCompleter,
TestComponentBuilder,
By,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit
} from 'angular2/test';
import { MockBackend, MockConnection, BaseRequestOptions, Http, Response } from 'angular2/http';
import { Injector, bind } from 'angular2/angular2';
import { ObservableWrapper } from 'angular2/src/core/facade/async'
import { CoreService } from 'public/services/core'
export function main() {
describe('public/services/core', () => {
let backend: MockBackend;
let response: Response;
let coreService: CoreService;
let injector: Injector;
afterEach(() => backend.verifyNoPendingRequests());
it('should get status', inject([AsyncTestCompleter], (async) => {
injector = Injector.resolveAndCreate([
BaseRequestOptions,
MockBackend,
bind(Http).toFactory((backend, options) => {
return new Http(backend, options)
}, [MockBackend, BaseRequestOptions]),
bind(CoreService).toFactory((http) => {
return new CoreService(http);
}, [Http])
]);
backend = injector.get(MockBackend);
coreService = injector.get(CoreService);
response = new Response('foo');
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
expect(c.request.url).toBe('/api/status');
c.mockRespond(response);
});
// attempt #1: fails because res argument is undefined
coreService.getStatus().subscribe(res => {
expect(res).toBe('');
async.done();
});
// attempt #2: fails because emitter.observer is not a function
ObservableWrapper.subscribe(coreService.getStatus(), res => {
expect(res).toBe('');
async.done();
});
}));
});
}
Related:
https://github.com/angular/angular/issues/3502
https://github.com/angular/angular/issues/3530
I just found this topic while looking for testing tips but I can't see a direct answer to that so...
This one is based on Angular RC.1
Testing service with Mock Backend
Let's say your service is:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
#Injectable()
export class CoreService {
constructor(private http: Http) {}
getStatus() {
return this.http.get('/api/status');
}
}
Test to the service above will look like this:
import {
beforeEach,
beforeEachProviders,
describe,
expect,
inject,
it,
} from '#angular/core/testing';
import { provide } from '#angular/core';
import { BaseRequestOptions, Response, ResponseOptions } from '#angular/http';
import { MockBackend, MockConnection } from '#angular/http/testing';
describe('Http', () => {
beforeEachProviders(() => [
CoreService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
})
]);
beforeEach(inject([MockBackend], (backend: MockBackend) => {
const baseResponse = new Response(new ResponseOptions({ body: 'status' }));
backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse));
}));
it('should return response when subscribed to getStatus',
inject([CoreService], (coreService: CoreService) => {
coreService.getStatus().subscribe((res: Response) => {
expect(res.text()).toBe('status');
});
})
);
})
What you really have to look at there is to have proper mocking in beforeEachProviders. Test itself is quite simple and ends up with subscribing to the service method.
Note: Don't forget to set base providers first:
import { setBaseTestProviders } from '#angular/core/testing';
import {
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS,
TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
} from '#angular/platform-browser-dynamic/testing';
setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
Since asking this question we did upgrade to Angular2 RC 1. Our imports look like Wojciech Kwiatek's (thank you for your answer!) but our testing strategy is slightly different. We wanted to assert on the request as well as the response. Instead of using beforeEachProviders(), we used beforeEach() where we create our own injector and save a reference to the service-under-test and mock backend. This allows us to assert on the request and manage the response inside the test, and it lets us use the verifyNoPendingRequests() method after each test.
describe('core-service', () => {
let service: CoreService;
let backend: MockBackend;
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate(<any> [
CoreService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: (mockBackend, defaultOptions) => new Http(mockBackend, defaultOptions),
deps: [MockBackend, BaseRequestOptions]
})
]);
service = <CoreService> injector.get(CoreService);
backend = <MockBackend> injector.get(MockBackend);
});
afterEach(() => backend.verifyNoPendingRequests());
it('should get status', () => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toEqual('api/status');
c.mockRespond(new Response(new ResponseOptions({ body: 'all is well' })));
});
service.getStatus().subscribe((status) => {
expect(status).toEqual('all is well');
});
}));
});
Edit: Plunker updated to RC2.
https://plnkr.co/edit/nlvUZVhKEr8d2mz8KQah?p=preview

Resources