here is my complete code function example:
public scan(formData: Object): Observable<any> {
let url = this.remoteUrl;
let result;
this.onlineService.isOnline$.subscribe( (isOnline) => {
if (isOnline) {
console.log('services is online connected');
result = this
._http
.post(url, formData, { headers: headers })
.pipe(map((res: any) => {
// console.log(res);
let response = res;
return response;
}),
catchError(error => {
if (error.status === 401 || error.status === 403) {
// handle error
}
return throwError(error);
}));
}else{
console.log('services are offline');
result = this.dbService.getByIndex('safety', 'code', formData['trafoStation']).subscribe( (location) => {
return location;
});
}
});
console.log(result);
return result;
};
actually, I need to run two different services based on an internet connection if the connection is available then call server API otherwise store on offline ngx-indexed-db.
i have stored data both online and offline.
getting undefined in result.
Result is undefined because it's an async operation: this.onlineService.isOnline$ has not emmited yet, but you already have return result, thus the undefined.
Also, the way you combine your observables is not right. You should NOT create new observables (and subscribe to them) in a subscribe method. That lead to weird side effects and memory leaks down the line.
Here's my proposal to get your code to work. I used the switchMap operator to return either your apiCall or your store operation based on isOnline$ value. SwitchMap is used to combine a higher observable with an inner observable and flatten the stream. It will also interupt the current subscription each time isOnline$ emits:
private _handleServices(formData, isOnline: boolean): Observable<any> {
console.log(`services are ${isOnline ? 'online': 'offline'}`);
const url = this.remoteUrl;
const apiCall$ = this._http.post(url, formData, { headers: headers })
.pipe(
catchError(error => {
if (error.status === 401 || error.status === 403) {
// handle error
}
return throwError(error);
})
);
const store$ = this.dbService.getByIndex('safety', 'code', formData['trafoStation']);
return (isOnline) ? apiCall$ : store$;
}
public scan(formData: Object): Observable<any> {
return this.onlineService.isOnline$.pipe(
switchMap((isOnline) => this._handleServices(formData, isOnline)),
tap(res => console.log(res))
);
};
Then, when you call your function in your component, you will call it like this:
this.scan(formData).subscribe(res => /* handle scan response */);
Related
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;
})
);
}
I've read a lot of documentation, articles and different thread about how to nest observers in RxJs and Angular, I still missing something and not able to get a result at the end.
Here is my code :
page.ts
export class LiabilitiesPage implements OnInit {
constructor(
private liabilityService: LiabilityService,
private router: Router
) {}
refreshLiabilities() {
// Get the liabilities
console.log('refreshing') // passing there
this.liabilityService.getAllLiabilities().subscribe(
(response: Liability[]) => {
console.log(response); // <=== Never pass there !
if (response) {
this.liabilities = response;
} else {
// empty response code
}
}, error => {
// response error code (never passing there either)
}
}
}
liability.service.ts
// all the needed imports
#Injectable({
providedIn: 'root'
})
export class LiabilityService {
constructor(
private authService: AuthService,
private http: HttpClient,
) {}
// first try : Do not send the http request
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions()
.pipe(
tap(options => this.http.get<Liability[]>(this.url + 'me/', options))
);
}
// try 2 : Doesn't work either
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions()
.pipe(
switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options)), // at this point I tried pretty much every operators (map, mergeMap etc.)
withLatestFrom()
);
}
/* this code was working before that I transformed the authService.getOptions in observable (it was just returning the options synchronyously before)
getAllLiabilities(): Observable<Liability[]> {
return this.http.get<Liability[]>(this.url + 'me/', this.authService.getOptions());
}*/
}
auth.service.ts
public getOptions(): Observable<any> {
return new Observable((observer) => {
this.storage.get('authToken').then((token) => {
console.log('passing') // Pass here
if (token && typeof token.auth_token !== 'undefined') {
console.log('passing') // pass here as well
this.isLoggedIn = true;
this.token = token.auth_token;
}
// it is returning the value
return {
headers: this.headers.set('Authorization', 'Bearer ' + this.token),
params: new HttpParams()
};
})
});
}
I tried almost all the possible operator combinations to make it works in the liabilityService without any success.
Problem :
The problem is that my page.ts subscribes to the this.http.get<Liability[]>(this.url + 'me/', options) observer but none xhr request is fired. The http get observer is never executed and I don't understand what I'm missing there.
I'm just starting experimenting Angular, but if I understood correctly the operators should do the mapping and flattening but this looks to never happen.
Bonus question :
I'm not catching either why the initial code :
return this.http.get<Liability[]>(this.url + 'me/', this.authService.getOptions());
is returning an Observable<Liability[]>
and with the switchMap :
switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options))
It is returning a Observable<HttpEvent<Liability[]>>
If somebody has a clue and the time to answer me on that, it would be amazing
You have a problem in the promise callback then():
this.storage.get('authToken').then((token) => {
return something; // this won't work.
})
instead you can use from, which will convert your promise to an observable.
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
public getOptions(): Observable<any> {
return from(this.storage.get('authToken')).pipe(map(token => {
return headers with token.
}));
}
So you could rewrite your code like this:
auth service:
private token: string | null = null;
public getOptions(): Observable<any> {
return this.getToken().pipe(
map(token => {
return {
headers: this.headers.set('Authorization', 'Bearer ' + token),
params: new HttpParams()
};
})
);
}
private getToken(): Observable<string | null> {
if (this.token) {
return of(this.token);
}
return from(this.storage.get('authToken')).pipe(
map(token => token?.authToken || null),
tap(token => this.token = token)
);
}
then you can use a switchmap:
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions().pipe(
switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options))
);
}
Update
The reason for getting HttpEvent<T> is because when the overload of .get() receives an any object it leaves the http event handling entirely up to you.
If you want it to return the provided element type, you have to satisfy the proper overload.
You can achieve that doing it like so:
Instead of returning the entire options, we only return the headers, which should be enough, because we do not really have enough to say about the rest of the options.
auth service
private token: string | null = null;
public createTokenHeaders(): Observable<HttpHeaders> {
const headers = new HttpHeaders();
return addToken(headers);
}
public addToken(headers: HttpHeaders): Observable<HttpHeaders> {
return this.getToken().pipe(
map(token => headers.set('Authorization', 'Bearer ' + (token || '')))
);
}
private getToken(): Observable<string | null> {
if (this.token) {
return of(this.token);
}
return from(this.storage.get('authToken')).pipe(
map(token => token?.authToken || null),
tap(token => this.token = token)
);
}
Then use it like so:
getAllLiabilities(): Observable<Liability[]> {
const url = this.url + 'me/';
const headers = new HttpHeaders();
return this.authService.addToken(headers).pipe(
switchMap(updatedHeaders => this.http.get<Liability[]>(url, { headers: updatedHeaders }))
);
}
or:
getAllLiabilities(): Observable<Liability[]> {
const url = this.url + 'me/';
return this.authService.createTokenHeaders().pipe(
switchMap(headers => this.http.get<Liability[]>(url, { headers }))
);
}
Note: Make sure you use the headers returned from the call to addToken. Reusing your own instantiated headers will not work because setting a header always returns a new HttpHeaders object. It is immutable.
StackBlitz Example
I have the function below:
getUser(userId: string) : Observable<any> {
var getUserURL = `/api/users/${userId}`,
getUserStatus = this.getUserStatus();
return this.http.get<any>(getUserURL).pipe(
catchError(this.handleError('getUser - Get User failed', null))
);
}
This returns the data from response from REST API for this URL /api/users/${userId} to toPromise
Is there any way that I can return the object that include the response data from REST API + local data(getUserStatus) which is Boolean in this case.
Thanks
Your request is not returning a Promise unless it isn't included in your example. You have an Observable.
Assuming I understand your question correctly. If you want to return the HTTP response + the local status you would do something like this using piped operators:
Service
getUser(userId: string) : Observable<any> {
const path = `/api/users/${userId}`
const userStatus = this.getUserStatus();
return this.http.get<any>(path)
.pipe(
map((response: any) => {
return { response, userStatus };
}),
catchError(this.handleError('[USER]::getUser Failed', null))
);
}
Component
this.getUser(1000).subscribe(({response, userStatus}) => {
// Handle response and status
console.log('RESPONSE', response);
console.log('USER_STATUS', userStatus);
});
Like #mtputlz said you're returning an Observable. If you want to return your reponse which is in your case a boolean. Then we can convert the obserable to a promise:
async getUser(userId: string) : any {
var getUserURL = `/api/users/${userId}`,
getUserStatus = this.getUserStatus();
return await this.http.get<any>(getUserURL).pipe(
catchError(this.handleError('getUser - Get User failed', null))
).toPromise();
}
From my Typescript code, I invoke a webservice written in C#. My typescript code looks like this, and it works as expected when my service returns HTTP200, but when the server rejects the credentials and throws HTTP 400, it will not break inside the map function.
return this.http.post(this.authenticationEndpoint, params)
.map((response:Response) => {
let resp = response;
let token = response.json() && response.json().access_token;
if(token){
this.token = token;
localStorage.setItem('user', JSON.stringify({userName: userName, token:token}));
return true;
}
return false;
})
Looking at the definition of the Response class this defines properties like status, statusText and so on. Given my limited knowledge of Angular and Typescript I would assume that regardless of the Http code returned from my service, it will break inside the map function? How can I handle this case? My function return an Observable<boolean>
You need to catch the Observable Errors here's an Example:
export class ApiGateway {
baseURL = "https://myapi.com"; // or sometimes pulled from another file
constructor(private http: Http) {}
get(path, params) {
showLoadingIndicator();
let headers = this.createMySpecialHeaders();
let options = {
headers: headers
} // and whatever else you need to generalize
let fullUrl = this.baseUrl + path + '?' + this.urlEncode(params)
`;
return this.get(path, params, options)
.do(() => hideLoadingIndicator())
.map(res => res.json())
.catch(err => {
hideLoadingIndicator();
// show error message or whatever the app does with API errors etc
// sometimes rethrow the error, depending on the use case
})
}
}
I have a scenario where I have to get the request payload passed when the service fails so I can return back along with error response. My code goes like below.
#Effect() doGetEvents$: Observable<Action> = this.actions$
.ofType(EVENTS)
.switchMap((action) => {
let eventDate = action.payload.date;
return this.http.service(action.payload);
})
.map(res => {
// success
if (res.status) {
return CustomActions.prototype.eventsResponse({ type: EVENTS_RESPONSE, payload: res.payload });
}
//failure
return CustomActions.prototype.EventsErrorResponse({
type: CustomActions.EVENTS_ERROR_RESPONSE,
payload: {
status: res.status,
errorMessage: res.errorMessage,
billDate: '10/01/2016', // <--- I need the eventDate got from the above switchMap
errorType: CustomActions.EVENTS + '_ERROR'
}
});
});
I tried passing like
.switchMap((action) => {
let eventDate = action.payload.date;
return [eventDate, this.http.service(action.payload)];
})
but this won't execute the http call and won't return the response on .map() args.
Also the are options to make the eventDate outside the scope of Effects and assign it when service fails but it is not a cleaner approach, there should be some way passing data round not sure what I missed!
If you want to include information from the payload, along with the HTTP service's result, you can use the map operator, like this:
.switchMap((action) => {
return this.http
.service(action.payload)
.map(result => [action.payload.date, result]);
})
.map(([date, result]) => { ... })