Search code examples
angularjstypescriptmouseevent

Angular , how to handle when user click button or submit multiple times?


How can I prevent users from submitting a form multiple times? My current issue right now is when the user clicked the submit button multiple times it will create multiple users. It should create a user only once and wait before creating another user.

Here is what I have:

<button mat-flat-button color="primary" [disabled]="userStatus == 'USER_EXISTS_ON_CURRENT_ACCOUNT'" (click)="createUser()">Create
                        User</button>

TypeScript:

createUser() {
    this.accountService.create(this.modelForm.value).pipe(
      finalize(() => {
        this.isInProgress = false;
      })
    ).subscribe({next: (res) => { this.notificationService.showSuccess('User has been created successfully.');
        this._router.navigate(['settings/user']);
      },
      error: (err) => {this.notificationService.showError('Something went wrong, Try again later.');
        this.isInProgress = false;
      },
      complete: () => {
        this.isInProgress = false;
      },
    });
  }

Solution

  • I have slightly updated your code,

    1 - We will have to create a User button in the template And

        <button #createUserBtn mat-flat-button color="primary" [disabled]="userStatus == 'USER_EXISTS_ON_CURRENT_ACCOUNT'"> CreateUser </button>
    

    2 - Access Create User button in .ts file

    @ViewChild('createUserBtn', {static:true}) button;
    

    3 - Create variable clicks$ to store click events

    clicks$: Observable<any>;
    

    4 - In ngOnInit: Initialize clicks$ variable to listen click events

    this.clicks$ = fromEvent(this.button.nativeElement, 'click');
    

    5 - In ngOnInit: On every click event i.e from click$ we will pass our event to exhaustMap

    The beauty of exhaustMap is once the first (outer observable) event is triggered it will stop listening to events(Outer Observable) untill it completes its inner observable

    So in our case when the user clicks on the button the first time(event), the exhaustMap will stop listening to the button click events until it completes our API call createUser(). This API call observable we will handle using the handleResponse() method.

    ngOnInit() {
        this.clicks$ = fromEvent(this.button.nativeElement, 'click');
        
        const result$ = this.clicks$.pipe(
            tap(x => console.log('clicked.')),
            exhaustMap(ev => {
                console.log(`processing API call`);
                return this.createUser();
            })
        );
        
        result$.subscribe(this.handleResponse());
    }
    

    Create User API Call

    createUser(): Observable<any> {
        return this.accountService.create(this.modelForm.value).pipe(
          finalize(() => (this.isInProgress = false))
        );
      }
    

    To handle response

    handleResponse(): any {
        return {
          next: res => {
            this.notificationService.showSuccess('User has been created successfully.');
            this._router.navigate(['settings/user']);
          },
          error: err => {
            this.notificationService.showError('Something went wrong, Try again later.');
            this.isInProgress = false;
          }
          complete: () => this.isInProgress = false;
        };
      }
    

    Demo

    If you can't access button you can move ngOnit Code to AfterViewInit Let me know if there is any error because i have not fully tested your code.

     ngAfterViewInit(): void {
        fromEvent(this.button.nativeElement, 'click')
          .pipe(
            tap(x => console.log('clicked.')),
            exhaustMap(ev => {
              console.log(`processing API call`);
              return this.createUser();
            })
          )
          .pipe(tap(x => console.log('Api call completed....')))
          .subscribe(this.handleResponse());
      }