What are the best practices for managing repeated button clicks or submissions in Angular?

Is there a way to prevent multiple form submissions by users? Currently, I am facing an issue where clicking the submit button multiple times results in the creation of duplicate users. Ideally, I'd like the system to create a user only once and then wait before allowing for another user to be created.

Here is my current code snippet:

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

Answer №1

I have made some slight modifications to your code:

1 - Firstly, we need to add a User button in the template And

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

2 - Next step is to access the Create User button in the .ts file

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

3 - We create a variable clicks$ to store click events

clicks$: Observable<any>;

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

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

5 - Also in the ngOnInit function: On every click event from click$, we will utilize exhaustMap

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

In our scenario, when the user clicks on the button for the first time(event), the exhaustMap will cease listening to the button click events until it finishes our API call createUser(). This API call observable will be managed 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());
}

Make the Create User API Call

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

To handle the 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;
    };
  }

Check out the Demo

If you encounter issues accessing the button, you can move the ngOnInit Code to AfterViewInit Let me know if there are any errors as 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());
  }

Answer №2

For the feature where the user must wait for the API response before clicking the button again, you can implement the following steps:

In your component.html file,

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

In your component.ts file,

  • Initialize a boolean variable:

    disableUserCreation: boolean = false;

  • Define the function below:

isButtonDisabled(): boolean {
    if (this.userStatus == 'USER_EXISTS_ON_CURRENT_ACCOUNT' || this.disableUserCreation) {
        return true;
    }
    return false;
}

Then,

createUser() {
    this.disableUserCreation = true;
    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;
        this.disableUserCreation = false;
      },
    });
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Updating Kendo by modifying the Angular model

While working on a project with Angular, I recently discovered the Kendo-Angular project available at . I successfully integrated Angular-Kendo into my project and it seems to be functioning well, except for updating models in the way I am accustomed to. ...

Issue with ngClass not updating during Route Event

I am using a bottomNavigation component that changes its style to indicate which route we are currently on. Below is the template for the bottom-navigation: class="menu-icon" [ngClass]="{ 'active-item': buttonActivated.value == '/my-goa ...

Transforming Form Field Inputs into Model Data Types

Looking at my Angular 8 component, there is a form that allows users to create a post. The PostModel consists of three properties: a string, a boolean, and a number: export interface PostModel { title: string year: number; approved: Boolean; } T ...

Is it possible to include external JSON data in the final deployed package?

I am currently working on an angular library that is ready for publication. The issue I'm facing is that it relies on a json file imported from a private npm directory, which requires VPN access. To avoid this dependency and eliminate the need for acc ...

Executing functions between controllers in AngularJS via events

I am facing a challenge with syncing data between two controllers in an AngularJS module. The real-time function I am building requires both controllers to be in sync when an action is executed. I have tried using Angular's $emit and $on to trigger fu ...

The script type `(close: any) => Element` cannot be assigned to type `ReactNode`

Encountering a problem while adding a popup in my TypeScript code Error: Type '(close: any) => Element' is not compatible with type 'ReactNode'. <Popup trigger={ <button className="fixed bott ...

Deleting lines from JSON object using Angular or JavaScript

On my webpage, I have a list where you can add a new line by pressing the "+" button (easy with push), but I'm not sure how to remove lines using the "X" button. https://i.stack.imgur.com/nm06A.png This is the JSON structure: "priceExtra" : [ ...

What steps are needed to enable WebStorm's autocompletion for external libraries?

As a beginner user of WebStorm and TypeScript, I am currently experimenting with incorporating the libstl library into my code. The snippet below is what I have written so far: var PriorityQueue = require('libstl').PriorityQueue; var queue = ne ...

Assistance required in creating a numerical list from an array of objects

I'm currently facing an issue with creating a numbered list from an array of objects. Below, you'll find the code containing the objects. You need to add the necessary TS code to display the atom names along with their weights in a numbered list ...

Difficulty in converting class-based JS useState array to TypeScript Hook

Recently, I've delved into the world of React / TypeScript and have taken on a personal project to enhance my skills. As part of this project, I am in the process of transitioning some class-based code into hooks. Here is the (JS) class-based code th ...

Is it possible to reactivate a stripe subscription that has been previously canceled

Currently, I am working on incorporating the functionality of resuming cancelled Stripe subscriptions. To achieve this, I am referring to the comprehensive guide available at: https://stripe.com/docs/billing/subscriptions/canceling-pausing Here is my appr ...

Learn the steps to integrate Infinite Scrolling with Node.js, Angular.js, and Firebase!

UPDATE 8: CODE: <% include ../partials/header %> <script src="https://www.gstatic.com/firebasejs/3.5.2/firebase.js"></script> <script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script> & ...

What could be the reason for the lack of impact when assigning a [dateClass] in mat-calendar?

I've been trying to customize the appearance of specific days in the mat-calendar component from Angular Material, but I'm having trouble getting it to work. I discovered the dateClass property which seemed like the right solution, but no matter ...

Associate the keys of a map interface with an array containing key-value pairs

Consider a scenario where we have an interface For example: interface Person { name: string; age: number ; } We aim to develop a generic function that can take the following parameters const res = Result.combineValues<Person>( { age: 1 ...

Unexpected behavior when trying to catch errors in Node.js await block

After logging, my node process exited unexpectedly E callback: [Function: RP$callback], E { serviceName: '....', E errno: 'EAI_AGAIN', E name: 'RequestError', I had anticipated that the code below would handle ex ...

What is the most effective way to retrieve a specific type of sibling property in TypeScript?

Consider the code snippet below: const useExample = (options: { Component: React.ComponentType props: React.ComponentProps<typeof options.Component> }) => { return } const Foo = (props: {bar: string; baz: number}) => <></& ...

Updating Angular components manually involves making direct changes to the component code,

Whenever I refresh a component in my Angular application implemented with version 17, the URL 'localhost:4200/(path)' reverts back to 'localhost:4200'. Interestingly, this behavior is consistent across three components except for the Ho ...

What is the best way to arrange an array of words expressing numerical values?

Is there a way to alphabetize numbers written as words (one, two, three) in Javascript using Angularjs? I need to organize my array of numeric words $scope.Numbers = ["three", "one", "five", "two", ...... "hundred", "four"]; The desired output should be ...

Triggered by each user interaction, the callback function is activated

I need to add a timeout feature to my AngularJS web application. Each time a user interacts with the site, a timer is set for 10 minutes. Once this timer runs out on the client-side, a request is sent to the server signaling a timeout. What is the best wa ...

The function designed to verify a row in the database using a session variable is experiencing technical difficulties

Currently, I am developing a web application using PHP and AngularJS. My objective is to restrict access to a specific section of the navbar for users with special privileges, such as administrators. In order to achieve this, I have implemented an Angula ...