Munchkin
Munchkin

Reputation: 1085

How do I properly implement a once in a time frame click activated action button using RxJS?

I'm still learning RxJS. For a project of mine I wanted to implement this "save only once" functionality.

The code snippet from a video about RxJs is the following:

const clickToSave$ = saveClick$.pipe(
    exhaustMap(() => http.post(saveURL, data) )
)

I have the following rewritten code in which I tried to implement a code snippet into my existing codebase.

 const saveClick$ = new Observable();
    // TODO: error handling
    const clickToSave$ = saveClick$.pipe( exhaustMap(() => this.http.post(this.xmlendpointPdf, jsonType, this.httpOptions) )
      ).subscribe(
        result => {
          const docUrl = 'test';
          console.log(docUrl);        },
        error => {
          if (error['status'] === 0) {
            this.openAlertDialog('Unknown error');
          } else {
          this.openAlertDialog('The following error has appeared:' + error['statusText']);
          }
          console.log('There was an error: ', error);
        });
      }

...but it apparently doesn't work. How am I supposed to bind to a (click)= .... event in button's HTML like that? To recap, what I want is that you can theoretically click on the button as much as you want but it will do the action (which is http.post call in this case) only once for a time period so when the user spams the button with clicks. How can I successfully implement this code snippet for my own specific use case (which I imagine a lot of people in the future will look for too).

Upvotes: 0

Views: 193

Answers (1)

NateScript
NateScript

Reputation: 121

Feel free to ask questions if you need more explanation!

Template

<button (click)="bntClicked($event)">Save Button</button>
<br />
<button #btn>Save Button 2</button>

Code

export class AppComponent implements OnInit {
  //Solution 1 - Not a big fan of this one personally but it does work
  //Manually make sure a subscription finishes before allowing a new one
  activeSubscription: Subscription;
  bntClicked($event) {
    if (!this.activeSubscription) {
      this.activeSubscription = this.saveMock().subscribe({
        next: result => {
          console.log(result);
          this.resetSubscription();
        }
      });
    }
  }

  resetSubscription() {
    this.activeSubscription.unsubscribe();
    this.activeSubscription = undefined;
  }
  //End solution 1;

  //Solution 2 - I prefer this one

  //We get a reference to the button using ViewChild
  @ViewChild("btn", { static: true }) button: ElementRef;

  ngOnInit(): void {
    //Option 1
    fromEvent(this.button.nativeElement, "click")
      .pipe(
        //Cancel previous observable and subscribe to new/last one
        switchMap($event => this.saveMock())
      )
      .subscribe({
        next: result => console.log("switchMap", result)
      });

    //Option 2
    fromEvent(this.button.nativeElement, "click")
      .pipe(
        debounceTime(1000),
        mergeMap($event => this.saveMock())
      )
      .subscribe({
        next: result => console.log("debounceTime", result)
      });
  }
  //End solution 2;

  saveMock(): Observable<any> {
    return of({ description: "Hello World!" }).pipe(delay(1000));
  }
}

Resources

StackBlitz Example

switchMap - learn rjxs

debounceTime - learn rxjs

fromEvent - rxjs

Upvotes: 1

Related Questions