how to add delay in http request using angular? [duplicate] - javascript

I understand that Observable.debounce() can be used to process rapid fire form input. As Http GET also returns an Observable, I wonder it it is possible to debounce rapid http requests? I tried debounceTime() but it did not appear to do anything.
public getStuff(p1, area:string, p2:string): Observable<number> {
return this.http.get(some_url)
.map(r => r.json())
.debounceTime(10000)
.catch(this.handleError);
};

The debounceTime allows to buffer events and only handle the last one after an amount of time.
It's useful in the context of inputs but it should be defined on the observable that triggers the event not on the one created for the HTTP request.
Here is a example on a control associated with an input that leverages the debounceTime operator:
#Component({
(...)
template: `
<input [ngFormControl]="ctrl"/>
`
})
export class MyComponent {
constructor() {
this.ctrl = new Control();
this.ctrl.valueChanges
.debounceTime(500)
.distinctUntilChanged()
.switchMap((value: string) => {
// Get data according to the filled value
return this.service.getData(entry);
})
.subscribe(data => {
// Update the linked list
this.list = data;
});
}
}
This article could also interest you:
https://jaxenter.com/reactive-programming-http-and-angular-2-124560.html (see section "Linking with user events")
Following the micronyks's comment, here is an additional link:
Everything is a stream: http://slides.com/robwormald/everything-is-a-stream (youtube: https://www.youtube.com/watch?v=UHI0AzD_WfY)

You have to transform from subject observable into an http observable with switchMap like this:
observableObj$: Observable<any>;
subjectObj = new Subject();
ngOnInit() {
this.observableObj$ = this.subjectObj.pipe(
debounceTime(1000),
switchMap(() => {
...
return this.http.get(some_url).map(r => r.json());
}),
);
this.observableObj$.subscribe((data) => {
// result of http get...
...
});
}
getStuff() {
this.subjectObj.next();
}

in Angular7:
import { Observable, of, timer } from 'rxjs';
import { catchError, retry, map, debounce } from 'rxjs/operators';
public getStuff(p1, area:string, p2:string): Observable<number> {
return this.http.get(some_url)
.pipe(
debounce(() => timer(10000)),
catchError(this.handleError)
);
};

Related

NestJS Http Module

I took this example from the nestJS docs (https://docs.nestjs.com/techniques/http-module#http-module), which is a minimal example of my problem:
#Injectable()
export class CatsService {
constructor(private httpService: HttpService) {}
findAll(): Observable<AxiosResponse<Cat[]>> {
return this.httpService.get('http://localhost:3000/cats');
}
}
How can I extract the actual Array of Cats from the Observable<AxiosResponse<Cat[]>>?
I tried the following but it gives me a subscriper object which I also don't know how to unwrap to get the actual data.
const cats = await this.catsService.findAll().subscribe((val) => val);
Using .toPromise() is deprecated now check this : https://rxjs.dev/deprecations/to-promise
Listing below is an example using the new approach
UPDATE Oct 2021
import { lastValueFrom } from 'rxjs';
.
.
.
const observable = await this.httpService.get('url').pipe(map((res) => res.data));
// you can use the data object now !!
const data = await lastValueFrom(observable);
#Injectable()
export class CatsService {
constructor(private httpService: HttpService) {}
findAll(): Promise<Cat[]> {
return this.httpService.get('http://localhost:3000/cats').toPromise();
}
}
const cats = await this.catsService.findAll().data
worked for me.
Try this:
findAll(): Observable<Cat[]> {
return this.httpService.get('http://localhost:3000/cats')
.pipe(map(response => response.data);
}

How to implement a paging solution using RxJs?

What is the best way to implement a paging solution in RxJs?
If want to have an observable that emits new data based on an event (could be a click, or a JavaScript function call). For instance if I have an Observable retrieving data from a Web API, and I don't want it to just keep on hammering away HTTP requests, I only want that to happen on an event triggered by the subscriber. Scenarios could be infinite scrolls (event triggered by scrolling), classic paging (event triggered by user clicking on next page) etc.
This is the solution I came up with based on the comments and using RxJs 6.3.3
export default class Test extends React.Component<any, any> {
private subscription: Subscription;
public componentDidMount() {
const client = new MyClient();
let source = client.get('/data');
const buttonNotifier = defer(() => {
console.log("waiting");
return fromEventPattern(
(handler) => { this.next = handler; }
).pipe(tap(() =>
console.log("sending more data")
));
});
const pagedData: Observable<{Value:any, NextLink:string}> = source.pipe(
expand(({ NextLink }) => NextLink ?
buttonNotifier.pipe(take(1), concatMap(() => client.get(NextLink))) :
empty()));
this.subscription = pagedData.subscribe(
result => {
this.setState({
Value: result.Value,
});
},
error => {
this.setState({
Error: `ERROR: ${error}`
});
},
() => {
this.setState({
Done: `DONE`
});
}
);
}
public componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
private next: Function;
public render(): React.ReactElement<any> {
return (<button onClick={()=> {this.next();}}>Next</button>);
}
}

Retrying a polling service with n seconds delay

private pollSubscriptions: Subscription;
private defaultPollTime: number = 2000;
constructor(
private http: HttpClient,
) {
this.pollSubscriptions = new Subscription();
}
pollRequest<T>(
url: string,
updateStatus: any,
pollWhileCondition: Function,
onPollingSuccessCallback?: Function,
timer = this.defaultPollTime
) {
this.pollSubscriptions.add(timer(0, 2000).pipe(
switchMap(() => this.http.get<T>(url).pipe(
catchError((error: any) => empty()))),
tap(updateStatus),
takeWhile(data => pollWhileCondition(data)))
.subscribe());
}
ngOnDestroy(): void {
this.pollSubscriptions.unsubscribe();
}
I am able to poll for multiple urls simultaneously. But how can I enhance current functionality so that I can meet the following requirements:
If a url which is polled gets failed then how can we retry that poll url with a delay of 3(n) secs for 3 times?
How can we add distinct operator on the urls being polled?
STILL NO SOLUTION
Thanks in advance
Hopefully this helps..
A couple of things:
You'll probably want to use a shared timer for your polling if you'd like all of your polls to happen at the same time. So I've added a pollWhen property below.
You're looking for retryWhen, it's a pretty tricky operator to understand, but basically it works like this: When you subscribe, it calls the function you pass it with an observable of potential errors, when an error is emitted, you're expected to scrub that into a "nexted" value to retry, an immediate completion (ex empty) to complete quietly, or an error (ex throwError) to kill the observable with an error.
class Component() {
/** ticks every 10 seconds */
pollWhen = timer(0, 10000)
.pipe(share());
private pollSubscriptions: Subscription;
constructor(
private http: HttpClient,
) {
this.pollSubscriptions = new Subscription();
}
pollRequest<T>(
url: string,
updateStatus: any,
pollWhileCondition: Function,
onPollingSuccessCallback?: Function,
timer = this.defaultPollTime
) {
this.pollSubscriptions.add(this.pollWhen.pipe(
switchMap(() =>
this.http.get<T>(url).pipe(
// Setup retries
retryWhen(
errors => errors.switchMap(
// if more than 3 retries,
// stop retrying quietly
(_, i) => i < 3
? timer(1000)
: EMPTY
)
)
)
),
tap(updateStatus),
takeWhile(pollWhileCondition)
).subscribe());
}
ngOnDestroy(): void {
this.pollSubscriptions.unsubscribe();
}
}
To answer your first question you can use retryWhen operator and replace catch.
For 2 question, method needs a bit of rewrite, you can change pollRequest to a subject() to store and emit the distinct url to the stream for processing.
var pollUrl = new rxjs.Subject()
const updateStatus = () => true
const pollWhileCondition = () => true
const http = url => {
console.log('http call...',url)
return rxjs.timer(1000).pipe(
rxjs.operators.tap(()=>{
throw "http call error"
})
)
}
const distinctUrl = pollUrl.pipe(rxjs.operators.distinct())
distinctUrl.pipe(
rxjs.operators.mergeMap(url => {
return rxjs.timer(0, 2000).pipe(rxjs.operators.map(() => url))
}),
rxjs.operators.tap(()=>console.log('xxx')),
rxjs.operators.mergeMap(url => http(url).pipe(
rxjs.operators.retry(3),
)),
rxjs.operators.catchError(()=>rxjs.empty()),
rxjs.operators.repeat()
).subscribe(()=>{},err=>{
console.warn(err)
},()=>console.log('comple'))
pollUrl.next('http://google.com')
setTimeout(()=> pollUrl.next('http://twitter.com') ,7000)
http://jsfiddle.net/cy0nbs3x/1535/
Not the complete solution but here I am able to achieve retrying 3 times with delay of 3 seconds. I am still seeking for how can I make the active poll urls distinct. Any help is much appreciated.
import { timer as observableTimer, Subscription, interval, of, concat, Observable } from 'rxjs';
import { takeWhile, tap, take, switchMap, repeat, retryWhen, scan, mapTo, expand, exhaustMap } from 'rxjs/operators';
#Injectable()
export class ReportPollingService {
private pollSubscriptions: Subscription;
constructor(private http: HttpClient){}
pollRequest<T>(url: string, updateStatus: any, pollWhileCondition: Function){
if (this.pollSubscriptions.closed) {
this.pollSubscriptions = new Subscription();// re-open polling
}
const request$ = this.http.get<T>(url);
const firstRequest$ = request$;
const polling$ = interval(options.interval).pipe(
take(1),
exhaustMap(() => request$),
repeat()
);
this.pollSubscriptions.add(concat(firstRequest$, polling$).pipe(
retryWhen(errors$ => {
return errors$.pipe(
scan(
({ errorCount, error }, err) => {
return { errorCount: errorCount + 1, error: err };
},
{ errorCount: 0, error: null }
),
switchMap(({ errorCount, error }) => {
if (errorCount >= 3) {
throw error;
}
return observableTimer(3000, null);
})
);
}),
).pipe(tap(updateStatus), takeWhile(data => pollWhileCondition(data))).subscribe());
}
stopPolling(): void {
this.pollSubscriptions.unsubscribe();
}

Angular Transfer State not preventing repeat http calls

I have the http request being made a service which is injected onto my component and subscribed to from there. Since I introduced server side rendering with angular universal to my application, the results on the page are repeated at least twice.
I have method which is called on click, which performs the http request to facebook's api
getAlbum(albumId: number) {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY, null as any);
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, res as any);
});
}
}
I declared the const variable below the imports
const ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
And I also declared the property
albumNames: any;
I am assuming I have done all of the imports right I have the code on github in the gallery component.
You are on the right pass, you just need to handle your service differently if you are on the server or the browser side to perform your queries only once and not twice.
Pseudo logic:
If server -> Do http request -> Set value in transfer-state
If browser -> Get value from transfer-state
To do so, you could for example enhance your Service like following:
#Injectable()
export class FacebookEventsService {
const ALBUM_PHOTOS_KEY: StateKey<number>;
constructor(#Inject(PLATFORM_ID) private platformId: Object, private http: HttpClient) {
this.ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
}
getBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
// Here we check if server or browser side
if (isPlatformServer(this.platformId)) {
return this.getServerBachaDiffFacebookEvents();
} else {
return this.getBrowserBachaDiffFacebookEvents();
}
}
getServerBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return this.http.get(this.facebookEventsUrl)
.map(res => {
// Here save also result in transfer-state
this.transferState.set(ALBUM_PHOTOS_KEY, calendarEvents);
});
}
getBrowserBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return new Observable(observer => {
observer.next(this.transferState.get(ALBUM_PHOTOS_KEY, null));
});
}
}
UPDATE
To use this logic you would also need:
TransferHttpCacheModule (to be initialized in app.module.ts).
TransferHttpCacheModule installs a Http interceptor that avoids
duplicate HttpClient requests on the client, for requests that were
already made when the application was rendered on the server side.
https://github.com/angular/universal/tree/master/modules/common
ServerTransferStateModule on the server side and BrowserTransferStateModule on the client side to use TransferState
https://angular.io/api/platform-browser/TransferState
P.S.: Note that if you do so and enhance your server, of course you would not need anymore to set the value in transfer-state in your getAlbum() method you displayed above
UPDATE 2
If you want to handle the server and browser side as you did in your gallery.component.ts, you could do something like the following:
getAlbum(albumId: number) {
if (isPlatformServer(this.platformId)) {
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, null);
});
}
} else {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY,null);
}
}
UPDATE 3
The thing is, your action getAlbum is never called on the server side. This action is only used on the browser side, once the page is rendered, when the user click on a specific action. Therefore, using transfer-state in that specific case isn't correct/needed.
Furthermore not sure that the Observable in your service was correctly subscribed.
Here what to change to make it running:
gallery.component.ts
getAlbum(albumId: number) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.albumPhotos = res;
});
}
facebook-events.service.ts
getBachadiffAlbumPhotos(albumId: number): Observable<Object> {
this.albumId = albumId;
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
return Observable.fromPromise(this.getPromiseBachaDiffAlbumPhotos(albumId));
}
private getPromiseBachaDiffAlbumPhotos(albumId: number): Promise<{}> {
return new Promise((resolve, reject) => {
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
let facebookPhotos: FacebookPhoto[] = new Array();
let facebookPhoto: FacebookPhoto;
const params: HttpParams = new HttpParams();
this.http.get(this.facebookAlbumPhotosUrl, {params: params})
.subscribe(res => {
let facebookPhotoData = res['data'];
for (let photo of facebookPhotoData) {
facebookPhotos.push(
facebookPhoto = {
id: photo.id,
image: photo.images[3].source,
link: photo.link,
height: photo.height,
width: photo.width
});
}
resolve(facebookPhotos);
}, (error) => {
reject(error);
});
});
}
UPDATE 4
ngOnInit is executed on the server side, this means that my very first answer here has to be use in this case.
Furthermore, also note that on the server side you doesn't have access to the window, therefore calling $
With gallery.component.ts you could do something like this to run only the http queries once but this won't solve all your problems, I think it will still need further improvements.
ngOnInit() {
if (isPlatformServer(this.platformId)) {
this.facebookService.getBachadiffFacebookVideos().subscribe(res => {
this.bachataVidsArray = res;
this.state.set(VIDEOS_KEY, res as any);
});
this.facebookService.getBachadiffFacebookLastClassPictures().subscribe(res => {
this.bachataPicsArray = res;
this.state.set(LAST_CLASS_PICTURES_KEY, res as any);
});
this.facebookService.getBachadiffAlbumNames().subscribe(res => {
this.bachataAlbumHeaderNames = res;
this.state.set(ALBUM_NAMES_KEY, res as any);
});
} else {
$('ul.tabs').tabs();
this.bachataVidsArray = this.state.get(VIDEOS_KEY, null as any);
this.bachataPicsArray = this.state.get(LAST_CLASS_PICTURES_KEY, null as any);
this.bachataAlbumHeaderNames = this.state.get(ALBUM_NAMES_KEY, null as any);
}
}

Array undefined on map and subscribe Ionic 2

I creted a service that I want to get data from a JSON file and assign to an array (countries) to use at entire application (a lots of Pages), but when I call getCountries method, the countries is undefined, what is wrong at my approach?
import { Http } from '#angular/http';
import { Injectable } from '#angular/core';
#Injectable()
export class CountryService {
private countries: any;
private isLoaded: boolean;
private url: string = 'http://localhost:8100/assets/data/countriesV2.json';
constructor(private http: Http) {
if (!this.isLoaded) {
this.http.get(this.url)
.map(res => res.json())
.subscribe(result => {
this.countries = result;
});
this.isLoaded = true;
}
}
public getCountries() {
console.log(this.countries);
return this.countries();
}
}
Maybe changing return this.countries(); to return this.countries; may help
Also, check that your result is not empty :
.subscribe(result => {
this.countries = result;
console.log(result)
});
You should always map the data in the service, and subscribe in your component. The reason why this.countries is undefined because it IS undefined, even though you are trying to do the request in the constructor, it's not going to work. Change your service to this:
#Injectable()
export class CountryService {
private url: string = 'http://localhost:8100/assets/data/countriesV2.json';
constructor(private http: Http) { }
public getCountries() {
this.http.get(this.url)
.map(res => res.json())
}
}
And then in your components you call getCountries and subscribe to the request.
countries: any[] = [];
constructor(private countryService: CountryService) { }
ngOnInit() {
this.countryService.getCountries()
.subscribe(data => {
this.countries = data;
// here countries is not undefined, so call your "randomCountry" method here!
this.getRandomCountry();
});
}
Since this is an async operation, I suggest you use the safe navigation operator in that view, that does not try to show the property of country in case country is null. More info here. So the usage would be:
<div *ngFor="let country of countries">
{{country?.yourProperty}} // replace "yourProperty" with your actual property
</div>
Some more detailed explanations about HTTP here from the official docs
Hope this helps! :)

Categories

Resources