NgRx effect, ones failure occurs, does not accept new actions - javascript

#Injectable()
export class UserEffects {
constructor(
private store$: Store<fromRoot.State>,
private actions$: Actions,
private router: Router,
private chatService: ChatService,
private authService: AuthService,
private db: AngularFireAuth,
private http: HttpClient
) { }
#Effect()
login$: Observable<Action> = this.actions$.pipe(
ofType(UserActions.LOGIN),
switchMap((action: UserActions.LoginAction) =>
from(this.db.auth.signInWithEmailAndPassword(action.payload.email, action.payload.password)),
),
// switchMapTo(from(this.db.auth.currentUser.getIdToken())),
switchMapTo(from(this.db.authState.pipe(
take(1),
switchMap((user)=>{
if (user)
return from(user.getIdToken());
else
return of(null);
})
))),
switchMap(token => this.http.post(environment.apiUrl + '/api/login', { token: token })),
tap((response: any) => {
if (response.valid === 'true') {
localStorage.setItem('token', response.token);
this.router.navigate(['dash']);
}
}),
map(response => new UserActions.LoginSuccessAction({ response })),
catchError(error => {
return of({
type: UserActions.LOGIN_FAILURE,
payload: error
});
})
);
#Effect({ dispatch: false })
loginSuccess$: Observable<Action> = this.actions$.pipe(
ofType(UserActions.LOGIN_SUCCESS),
tap((action: UserActions.LoginSuccessAction) => {
console.log("LOGIN_SUCCESS");
console.log(action);
}));
#Effect({ dispatch: false })
loginFailure$: Observable<Action> = this.actions$.pipe(
ofType(UserActions.LOGIN_FAILURE),
tap((action: UserActions.LoginFailureAction)=>{
console.log("LOGIN_FAILURE");
console.log(action.payload.code);
}));
}
Use case:
You are at a login page. You enter your username and password to call LoginAction. If all goes well, LoginSuccessAction should be called. Otherwise, LoginFailureAction should be called.
Everything works except one case:
Once LoginFailureAction is called once, neither LoginSuccessAction nor LoginFailureAction will be called again. I am at a loss of why this is happening. I think it may have something to do with the line:
catchError(error => {
return of({
type: UserActions.LOGIN_FAILURE,
payload: error
});
})
but here I am lost. I am not an expert on reactive programming, but it all works as it's supposed to except this one case. Does anyone have an idea why this occurs?

Effects respect successful completion and errors. In case of an error ngrx will resubscribe, in case case of a successful completion it will not.
For example catchError can cause this case, because it doesn't listen to its parent stream. If you want to enable stream in case of an error you need to use repeat operator right after catchError.
catchError(error => {
return of({
type: UserActions.LOGIN_FAILURE,
payload: error
});
}),
repeat(), // <- now effect is stable.

You will have to add catchError on each inner observable, e.g.
switchMap(token => this.http.post(environment.apiUrl + '/api/login', { token: token }).pipe(catchError(...)),
See the handling errors section in the NgRx docs for more info.

Related

how to use angular pipe and subscribe correctly in request call

In my angular application I am sending a request to my backend to check credentials, after success the backend sends an token which I read. So far this works, but I had to use an pipe to make it map to a method and then make it work. But my problem now it even though I am getting 200 from the server my page will not navigate to the protected page automatically. If I enter the url manually it works this is what I tried:
authenticateUser(login: LoginModel){
this.http = new HttpClient(this.handler)
return this.http.post<JwtToken>(environment.rootUrl + 'api/authenticate', {
username: login.username,
password: login.password,
}).pipe(map(response => this.authenticateSuccess(response)))
.subscribe({
next: () => {
this.isAuthenticated = true;
this.router.navigate(['/dashboard'])
}, error: (error) => {
this.isAuthenticated = false;
console.log(error)
}
})
}
It does not enter the subscribe part after the pipe. Is there any way to make this work? I still want to have an error handling like if no error then navigate to url if error do not navigate.
EDIT:
AuthenticateSuccess method:
isUserLoggedIn(){
return !! localStorage.getItem('authenticationToken')
}
private authenticateSuccess(response: JwtToken): void {
const jwt = response.id_token;
localStorage.setItem('authenticationToken' , jwt)
this.localStorageService.store('authenticationToken', jwt);
console.log(this.localStorageService.retrieve('authenticationToken'))
this.sessionStorageService.clear('authenticationToken');
}
Authguard:
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private auth: AuthenticationService,
private router: Router
) {
}
canActivate(): Promise<boolean> {
return new Promise(resolve => {
if (this.auth.isUserLoggedIn()) {
resolve(true)
} else {
this.router.navigate(['authenticate'])
resolve(false)
}
})
}
}
SOLUTION:
authenticateUser(login: LoginModel) {
this.http = new HttpClient(this.handler)
return this.http.post<JwtToken>(environment.rootUrl + 'api/authenticate', {
username: login.username,
password: login.password,
}).subscribe({
next: response => {
this.isAuthenticated = true;
this.authenticateSuccess(response)
this.router.navigate(['/dashboard'])
}, error: (err) => {
console.log(err)
}, complete: () => {
console.log("finished without worry")
}
})
}
RxJs map operator is supposed to modify the content of an observable. The map operator however needs to return the same observable or another observable, for the next subscribe operation to be able to function.
In your case your map operator does not return any observable at all and therefore the subscribe method has no reason to be triggered.
You could simple return the response again in your method here
private authenticateSuccess(response: JwtToken): any {
const jwt = response.id_token;
localStorage.setItem('authenticationToken' , jwt)
this.localStorageService.store('authenticationToken', jwt);
console.log(this.localStorageService.retrieve('authenticationToken'))
this.sessionStorageService.clear('authenticationToken');
return response;
}
but I think all the code of the map method matches better directly inside the subscribe method.

NGRX - dispatch action on catchError effect with different payload

I need to call an API that can return errors, warnings or success.
If it returns warnings the user must able to accept the warning and I should send the same payload + acceptWarning: true.
I need to display an ionic modal and wait for the user's response to see if he accepts or cancel the warning.
What should be the best way to achieve that?
Right now I have something like this:
#Effect()
public Assign$ = this.actions$.pipe(
ofType(myActions.Assign),
map(action => action.payload),
exhaustMap(assignment =>
this.assignService.assign(assignment).pipe(
switchMap(() => {
this.errorService.showPositiveToast(' Assigned Successfully');
return [
new LoadAssignments(),
new LoadOtherData()
];
}),
catchError(error =>
from(this.openErrorModal(error)).pipe(
switchMap(({ data = '' }) => {
if (data === 'Accept') {
return of(new Assign({ ...assignment, acceptWarning: true }));
}
return of(new AssignShipmentFailure());
})
)
)
)
)
);
async openErrorModal(response: any) {
const errorModal = await this.modalCtrl.create({
component: ErrorValidationPopup,
componentProps: {
response: response,
},
});
await errorModal.present();
return errorModal.onDidDismiss();
}
But it is not triggering the Assign action again. Thanks for your help
If any error occurred in the effect's observable (or any Observable), then its stream emitted no value and it immediately errored out. After the error, no completion occurred, and the Effect will stop working.
To keep the Effect working if any error occurred, you have to swichMap instead of exhaustMap, and handle the errors within the inner observable of the switchMap, so the main Observable won't be affected by that.
Why use switchMap?
The main difference between switchMap and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable
You can try something like the following:
#Effect()
public Assign$ = this.actions$.pipe(
ofType(myActions.Assign),
map(action => action.payload),
switchMap(assignment =>
this.assignService.assign(assignment).pipe(
switchMap(() => {
this.errorService.showPositiveToast('Assigned Successfully');
return [
new LoadAssignments(),
new LoadOtherData()
];
}),
catchError(error =>
from(this.openErrorModal(error)).pipe(
map(({ data = '' }) => {
if (data === 'Accept') {
return new Assign({ ...assignment, acceptWarning: true });
}
return new AssignShipmentFailure();
})
)
)
)
)
);

Best way for multiple HTTP Request in Angular

I am trying to send 2 HTTP requests one by one; if the first one is succeeds, send the second one, if not display the corresponding error message regarding to the first request.
I am planning to use something like that, but not sure if it is the best option for this scenario:
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
loadedCharacter: {};
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get('/api/people/1').subscribe(character => {
this.http.get(character.homeworld).subscribe(homeworld => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
});
}
}
I have different requests e.g. PUT and CREATE also using this approach. I know there are other ways e.g. forkjoin, mergemap, but if this one solves my problem seems to be more readable. Any idea?
First of all, your code works and that's great - you can leave it as is and everything will be fine.
On the other hand, there is a way for multiple improvements that will help you and your colleagues in future:
try to move http-related logic to the service instead of calling http in the components - this will help you to split the code into view-related logic and the business/fetching/transformation-related one.
try to avoid nested subscribes - not only you ignore the mighty power of Observables but also tie the code to a certain flow without an ability to reuse these lines somewhere in the application. Returning the Observable might help you with "sharing" the results of the request or transforming it in some way.
flatMap/mergeMap, concatMap and switchMap work in a different way, providing you an ability to control the behaviour the way you want. Though, for http.get() they work almost similar, it's a good idea to start learning those combining operators as soon as possible.
think about how you'll handle the errors in this case - what will happen if your first call will result an error? Observables have a powerful mechanism of dealing with them, while .subscribe allows you to handle an error only in one way.
An example using the switchMap:
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
loadedCharacter: {};
constructor(private http: HttpClient) {}
ngOnInit() {
const character$ = this.http.get('/api/people/1').pipe(
tap(character => this.characterWithoutHomeworld = character), // setting some "in-between" variable
switchMap(character => {
return this.http.get(character.homeworld).pipe(
map(homeworld => {
return {
...character,
homeworld: homeworld
}
}
)
)
}),
catchError(errorForFirstOrSecondCall => {
console.error('An error occurred: ', errorForFirstOrSecondCall);
// if you want to handle this error and return some empty data use:
// return of({});
// otherwise:
throw new Error('Error: ' + errorForFirstOrSecondCall.message);
})
);
// you can either store this variable as `this.character$` or immediately subscribe to it like:
character$.subscribe(loadedCharacter => {
this.loadedCharacter = loadedCharacter;
}, errorForFirstOrSecondCall => {
console.error('An error occurred: ', errorForFirstOrSecondCall);
})
}
}
2 nested subscriptions are never a way to go. I recommend this approach:
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.homeworld).pipe(
map(homeworld => ({ ...character, homeworld })),
)),
).subscribe(character => this.loadedCharacter = character);
Edit: For your university
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.university).pipe(
map(university => ({ ...character, university})),
)),
).subscribe(character => this.loadedCharacter = character);
Or even chain university and homeworld requests
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.homeworld).pipe(
map(homeworld => ({ ...character, homeworld })),
// catchError(err => of({ ...character, homeworld: dummyHomeworld })),
)),
switchMap(character => this.http.get(character.university).pipe(
map(university => ({ ...character, university})),
)),
).subscribe(character => this.loadedCharacter = character);
You can try a solution using switchmap and forkJoin for easier chaining and error handling. this will help keep the code clean in case the chain keeps growing into a deep nest.
this.http
.get("/api/people/1'")
.pipe(
catchError((err) => {
// handle error
}),
switchMap((character) => {
return forkJoin({
character: of(character),
homeworld: this.http.get(character.homeworld)
});
})
)
.subscribe(({ character, homeworld }) => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
EDIT: Scenario 2
this.http
.get("/api/people/1")
.pipe(
catchError((err) => {
console.log("e1", err);
}),
switchMap((character) => {
return forkJoin({
character: of(character),
homeworld: this.http.get(character.homeworld).pipe(
catchError((err) => {
console.log("e2", err);
})
)
});
})
)
.subscribe(({ character, homeworld }) => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
You can chain a catch error or add a separate function for error handling without it invoking the next API call. but I would recommend abstracting the backend logic to an angular service and using this method. which would help retain an easy to read structure.
You can check if the first request was successful or not by checking the status code:
ngOnInit() {
this.http.get('/api/people/1').subscribe((character: HttpResponse<any>) => {
// here you should look for the correct status code to check, in this example it's 200
if (character.status === 200) {
this.http.get(character.homeworld).subscribe(homeworld => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
} else {
// character is gonna contain the error
console.log(character)
}
});
}

Unsubscribe from Firestore stream - Angular

Recently I implemented Firebase into my Angular project and I have a question about unsubscribing from data stream.
When you use classic HTTP calls, observables from this are finite, but with the firestore, these are infinite, so you have to unsubscribe. But when I do so (after destroying component and log out), I still get an error in console and I can see that requests are still sending (or persisting).
In the network tab I can see this in the request timing:
Caution: request is not finished yet
This error pops up after I log out (Its probably caused because of my rules that I set)
FirebaseError: Missing or insufficient permissions
And also, I still get an error even if I use async pipe in angular.
Here is my current solution:
data.service.ts
items: Observable<any[]>;
constructor(db: AngularFirestore) {
this.items = db.collection("data").valueChanges();
}
getItems() {
return this.items;
}
home.component.ts
req: Subscription;
ngOnInit(): void {
this.req = this.dataService.getItems().subscribe(
res => {
console.log(res);
},
err => console.log(err),
() => console.log("completed")
);
}
ngOnDestroy(): void {
console.log("ya");
this.req.unsubscribe();
}
Thanks for your advices!
home.component.ts
import { takeWhile } from "rxjs/operators";
#Component({...})
export class HomeComponent implements OnInit, OnDestroy {
isAlive: boolean = true;
...
ngOnInit(): void {
this.dataService.getItems()
.pipe(takeWhile(() => this.isAlive))
.subscribe(res => {
console.log(res);
});
}
ngOnDestroy(): void {
this.isAlive = false;
}
}
takeWhile is what u needed
Use one of the rxjs operators, eg take (1) after taking the value, it unsubscribe by itself
this.dataService.getItems().pipe(take(1)).subscribe(
res => {
console.log(res);
},
err => console.log(err),
() => console.log("completed")
);

Getting Post angular 2 toPromise HTTP

Hello I need to get some response after posting json object, using toPromise, its my code, respond is undefined:
export class ApiStorage{
constructor( #Inject(Http) private http: Http ){}
rs(){
this.http.post('http://0.0.0.0:80/student/outbound', this.json, headers)
.toPromise()
.then(response => {
respond = JSON.stringify(response);
return respond; //<- edited
})
.catch((error: any) => {
...
});
}
}
then when in main component I use
send(){
respondJSON = apistorage.rs();
console.log(respondJSON);
}
respondJSON is undefined
respond will always be undefined in your code, because you are making an asynchronous call to a webservice, which you do not await before logging to console.
export class ApiStorage{
constructor( #Inject(Http) private http: Http ){}
rs() {
return this.http.post('http://0.0.0.0:80/student/outbound', this.json, headers)
.toPromise()
.then(response => {
let respond = JSON.stringify(response));
return respond;
})
.catch((error: any) => {
...
});
}
}
// rs now returns a promise, which can be used like this
// inside another function
send() {
apistorage.rs().then(res => {
console.log(res);
}
}

Categories