Reputation: 583
I have a timer in my main component of 3 seconds. inside the timer I perform http call-
constructor(){
this.timer = timer(3000, 3000);
this.timerObservable = this.timer.subscribe(x => {
this.http.get(URL).subscribe(()=>{
//DO SOMETHING
});
});
}
In another component I have a button that suppose to perform a different http call, pressing on the button invoke the sumbit function-
submit(){
this.http.get("/sumbitForm").subscribe(()=> {
//DO SOMETHING
})
}
When a user clicks on the button, if the timer is in process (the http inside of it was called and not resolved yet) I want to wait before I perform the http call on the button until it resolved, but if the timer is not in process (the time from the previous call did not passed yet) I want to execute it immediately.
I think that forkJoin and concat is not relevant here (this is a timer and not a 'regular' subscription that I want to wait to its execution either way) and I could not find a pretty way to do it, any idea?
Upvotes: 2
Views: 3482
Reputation: 14089
You need to share some information between your two components, i.e. when the polling request is in progress and when it isn't. You should use a Service for this. It's also always good practice to move your http request logic to a Service instead of using the HttpClient directly in the component. This allows you to do your general error handling in one place. Let's call this Service ApiService.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject, interval } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class ApiService {
// Use a BehaviorSubject to notify listeners about ongoing polling requests.
// A BahaviorSubject always has a current value that late subscribers will
// receive immediately upon subscribing
private pollRequestInProgress = new BehaviorSubject<boolean>(false);
// Share this BehaviorSubject as an Observable
public pollRequestInProgress$ = pollRequestInProgress.asObservable();
constructor(private http: HttpClient)
doPoll(url: string): Observable<any> {
return interval(3000).pipe( // interval(3000) is equivalent to timer(3000, 3000)
tap(_ => pollRequestInProgress.next(true)), // notify that a poll request is about to happen
switchMap(_ => this.http.get(url)), // do your poll request
tap(_ => pollRequestInProgress.next(false)) // notify that your poll request ended
);
}
}
This is the Component where you want to start your polling from.
private destroy$: Subject<void> = new Subject<void>();
constructor(private apiService: ApiService) {}
// move logic to ngOnInit instead of constructor
ngOnInit() {
// subscribe and thereby start the polling
this.apiService.doPoll(URL).pipe(takeUntil(this.destroy$))
.subscribe(pollResponse => {
//DO SOMETHING
});
}
ngOnDestroy() {
// unsubscribe when the Component gets destroyed.
this.destroy$.next();
this.destroy$.complete();
}
This is the Component where you want to perform a http request when you click a button.
constructor(private http: HttpClient, private apiService: apiService) {}
submit() {
// Listen to whether a polling request is currently in progress.
// We will immediately receive a value upon subscribing here, because we used
// a BehaviorSubject as the underlying source.
this.apiService.pollRequestInProgress$.pipe(
// Emit the first item that tells you that no polling request is in progress.
// i.e. let the first 'requestInProgress === false' through but not any
// other items before or after.
first(requestInProgress => !requestInProgress),
// If no polling request is in progress, switch to the http request you want
// to perform
switchMap(this.http.get("/sumbitForm")) // <-- consider moving this http.get to your ApiService aswell
).subscribe(httpResponse => {
// you've got your http response here
});
// you don't have to unsubscribe here as first and http.get both complete
// and thus unsubscribe automatically
}
Check out a simple example of the code logic above here: https://stackblitz.com/edit/rxjs-t4hjcr
Upvotes: 2
Reputation: 267
You can use Angular Subject
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class CallService {
private subject = new Subject<any>();
timerCompleted() {
this.subject.next();
}
checkTimer(): Observable<any> {
return this.subject.asObservable();
}
}
The app component uses the call service to subscribe to timer complete and make them available to the app component template.
import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { CallService } from './_services/index';
@Component({
selector: 'app',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnDestroy {
subscription: Subscription;
constructor(private callService: CallService) {
this.subscription = this.callService.checkTimer().subscribe(() => {
// call your api after timer complete
});
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
}
add below code in your timer
this.timer = timer(3000, 3000);
this.timerObservable = this.timer.subscribe(x => {
this.http.get(URL).subscribe(()=>{
this.callService.timerCompleted();
});
});
For more reference you can check http://jasonwatmore.com/post/2018/06/25/angular-6-communicating-between-components-with-observable-subject
Upvotes: 0