Best way for multiple HTTP Request in Angular - javascript

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)
}
});
}

Related

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();
})
)
)
)
)
);

Angular: how to return the first successful response from the list of http requests

I have a list of servers urls and making sequential http requests to them in a loop. When the success response arrives from the current request I want to break the loop and not to call all other servers. Could someone advice me how this could be handled in Angular/RxJS? Something like:
getClientData() {
for(let server of this.httpsServersList) {
var myObservable = this.queryData(server)
.pipe(
map((response: any) => {
const data = (response || '').trim();
if(data && this.dataIsCorrect(data)) {
return data; // **here I want to break from the loop!**
}
})
);
return myObservable;
}
}
private queryData(url: string) {
return this.http.get(url, { responseType: 'text' });
}
IMO it's better to avoid using a for loop for subscribing to multiple observables. It might lead to multiple open subscriptions. Common function used for this case is RxJS forkJoin. But given your specific condition, I'd suggest using RxJS from function with concatMap operator to iterator each element in order and takeWhile operator with it's inclusive argument set to true (thanks #Chris) to stop based on a condition and to return the last value.
import { from } from 'rxjs';
import { concatMap, filter, map, takeWhile } from 'rxjs/operators';
getClientData(): Observable<any> {
return from(this.httpsServersList).pipe(
concatMap((server: string) => this.queryData(server)),
map((response: any) => (response || '').trim()),
filter((data: string) => !!data && this.dataIsCorrect(data)) // <-- ignore empty or undefined and invalid data
takeWhile(((data: string) => // <-- close stream when data is valid and condition is true
!data || !this.dataIsCorrect(data)
), true)
);
}
Note: Try to tweak the condition inside the takeWhile predicate to match your requirement.
Edit 1: add inclusive argument in takeWhile opeartor
Edit 2: add additional condition in the filter operator
In angular we rely on RxJS operators for such complex calls
If you want to to call all of them in parallel then once one of them is fulfilled or rejected to cancel the other calls you should use
RxJS race learnrxjs.io/learn-rxjs/operators/combination/race
Or without RxJS you could use Promise.race
However if you want to call them in parallel and wait until first fulfilled "not rejected" or all of them rejected this is the case for Promise.any
Unfortunately no RxJS operator for it but on the follwoing article you could see how to implement this custom operator for Promise.any and an example for that operator
https://tmair.dev/blog/2020/08/promise-any-for-observables/
create a subject like this
responseArrived=new Subject();
and after pipe add takeuntil like this
var myObservable = this.queryData(server).pipe(takeUntil(responseArrived),map...
and in the line of code return data just call
responseArrived.next()
You can't use race because it will call all URLs in parallel, but you can use switchMap with recursive implementation
import { of, Observable, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators'
function getClientData(urls: string[]) {
// check if remaining urls
if (!urls.length) throw throwError(new Error('all urls have a error')); ;
return queryData(urls[0]).pipe(
switchMap((response) => {
const data = (response || '').trim();
if(data && this.dataIsCorrect(data))
// if response is correct, return an observable with the data
// for that we use of() observable
return of(data)
// if response is not correct, we call one more time the function with the next url
return getClientData(urls.slice(1))
}),
catchError(() => getClientData(urls.slice(1)))
);
}
function queryData(url: string): Observable<unknown> {
return this.http.get(url, { responseType: 'text' });
}
If your only condition is that you cancel requests once at least one response is received, can't just simply unsubscribe from the observable returned from the HttpClient call?
getData() {
const subscriptions = [];
[
'https://reqres.in/api/products/1',
'https://reqres.in/api/products/2',
'https://reqres.in/api/products/3',
].forEach((url, i) => {
subscriptions[i] = this.getClientData(url).subscribe(() => {
// Unsubscribe
subscriptions.forEach((v, j) => {
if (j !== i) {
console.log('Unsubscribe from ', j);
v.unsubscribe();
}
});
});
});
}
private getClientData(url: string) {
return this.httpClient.get(url, { responseType: 'text' }).pipe(
map((response: any) => {
const data = (response || '').trim();
if (data && true) return data;
return null;
})
);
}

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

#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.

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")
);

How to synchronise Angular2 http get?

I understand using observable I can execute a method when the request is completed, but how can i wait till a http get is completed and return the response using in ng2 http?
getAllUser(): Array<UserDTO> {
this.value = new Array<UserDTO>();
this.http.get("MY_URL")
.map(res => res.json())
.subscribe(
data => this.value = data,
err => console.log(err),
() => console.log("Completed")
);
return this.value;
}
the "value" will is null when its returned because get is async..
your service class: /project/app/services/sampleservice.ts
#Injectable()
export class SampleService {
constructor(private http: Http) {
}
private createAuthorizationHeader() {
return new Headers({'Authorization': 'Basic ZXBossffDFC++=='});
}
getAll(): Observable<any[]> {
const url='';
const active = 'status/active';
const header = { headers: this.createAuthorizationHeader() };
return this.http.get(url + active, header)
.map(
res => {
return res.json();
});
}
}
your component: /project/app/components/samplecomponent.ts
export class SampleComponent implements OnInit {
constructor(private sampleservice: SampleService) {
}
ngOnInit() {
this.dataset();
}
dataset(){
this.sampleservice.getAll().subscribe(
(res) => {
// map Your response with model class
// do Stuff Here or create method
this.create(res);
},
(err) => { }
);
}
create(data){
// do Your Stuff Here
}
}
By looking at the angular source (https://github.com/angular/angular/blob/master/packages/http/src/backends/xhr_backend.ts#L46), it is apparent that the async attribute of the XMLHttpRequest is not getting used. The third parameter of XMLHttpRequest needs to be set to "false" for synchronous requests.
Please find code for your problem
Below is component and service file.And Code is Working fine for synchornize
import { Component, OnInit } from '#angular/core';
import { LoginserviceService } from '../loginservice.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
model:any={};
constructor(private service : LoginserviceService) {
}
ngOnInit() {
}
save() {
this.service.callService(this.model.userName,this.model.passWord).
subscribe(
success => {
if(success) {
console.log("login Successfully done---------------------------- -");
this.model.success = "Login Successfully done";
}},
error => console.log("login did not work!")
);
}
}
Below is service file..
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { UserData } from './UserData';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/toPromise'
import {Observable} from 'rxjs/Rx'
#Injectable()
export class LoginserviceService {
userData = new UserData('','');
constructor(private http:Http) { }
callService(username:string,passwrod:string):Observable<boolean> {
var flag : boolean;
return (this.http.get('http://localhost:4200/data.json').
map(response => response.json())).
map(data => {
this.userData = data;
return this.loginAuthentication(username,passwrod);
});
}
loginAuthentication(username:string,passwrod:string):boolean{
if(username==this.userData.username && passwrod==this.userData.password){
console.log("Authentication successfully")
return true;
}else{
return false;
}
}
}
Another solution would be to implement a priority queue of sort.
From what I understand http requests do not get executed until you add subscribers. Therefore, you can do something like this:
Observable<Response> observable = http.get("/api/path", new RequestOptions({}));
requestPriorityQueue.add(HttpPriorityQueue.PRIORITY_HIGHEST, observable,
successResponse => { /* Handle code */ },
errorResponse => { /* Handle error */ });
This assumes that requestPriorityQueue is a service injected into your component. The priority queue would store entries in an array in the following format:
Array<{
observable: Observable<Response>,
successCallback: Function,
errorCallback: Function
}>
You would have to decide how the elements are added to your array. Finally, the following will happen in the background:
// HttpPriorityQueue#processQueue() called at a set interval to automatically process queue entries
The processQueue method would do something like this:
protected processQueue() {
if (this.queueIsBusy()) {
return;
}
let entry: {} = getNextEntry();
let observable: Observable<Response> = entry.observable;
this.setQueueToBusy(); // Sets queue to busy and triggers an internal request timeout counter.
observable.subscribe()
.map(response => {
this.setQueueToReady();
entry.successCallback(response);
})
.catch(error => {
this.setQueueToReady();
entry.errorCallback(error);
});
}
If you are able to add new dependencies you could try using the following NPM package: async-priority-queue
I looked and I couldn't find any way to make an HTTP call sync instead of async.
So the only way around this: wrap your call in a while loop with a flag. Don't let the code continue until that flag has "continue" value.
Pseudo code as follows:
let letsContinue = false;
//Call your Async Function
this.myAsyncFunc().subscribe(data => {
letsContinue = true;
};
while (!letsContinue) {
console.log('... log flooding.. while we wait..a setimeout might be better');
}
as you see, first callback waiting for a data from request and
there you can go on with your logic (or use the third one)
example:
.. subscribe( data => {
this.value = data;
doSomeOperation;
},
error => console.log(error),
() => {console.log("Completed");
or do operations here..;
}
});
How about to use $.ajax(of jQuery) or XMLHttpRequest.
It can use as asynchornize.
You should not try to make http calls behave synchronously. Never a good idea.
Coming to your getAllUser implementation it should return an observable from the function and the calling code should subscribe instead of you creating a subscription inside the method itself.
Something like
getAllUser(): Observable<UserDTO> {
return this.http.get("MY_URL")
.map(res => res.json());
}
In you calling code, you should subscribe and do whatever you want.

Categories