简体   繁体   中英

Angular 5, rxjs- wait for observable to finish only if it is in a middle of a run before running another process

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?

You need to share some information between your two components, ie 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.

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

MainComponent

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

FeatureComponent

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM