If I click fast on my submit-button the form is submitted two or more times. My thought was to prevent this with the disabled attribute, but I need variable disableButon
in every form like this:
@Component({
selector: 'example',
template: `
<form (submit)="submit()" >
<--! Some Inputs -->
<button [disabled]="disableButton" type="submit">Submit<button>
</form>
`
})
export class ExampleComponent {
private disableButton: boolean = false;
.......
submit(){
this.disableButton = true;
/*
* API call
*/
this.disableButton = false;
}
}
Am I doing this right or is there a more efficent/elegant way to do it?
The disabled property was first introduced by Microsoft but has since been adopted as a standard by the W3C. So when the form is submitted - either by clicking on the submit button or pressing Enter in a text input field - the submit button will be disabled to prevent double-clicking.
onsubmit = function () { if (allowSubmit) allowSubmit = false; else return false; } })(); (well, as sure-fire as you can get with JS enabled anyway). You could disabled the button as a visual confirmation to the end user that the form can only be submit once too. Very correct.
Angular reactive forms follow a model-driven approach to handle form input whose values can be changed over time. These are also known as model-driven forms. In reactive forms, you can create and update a simple form control, use multiple controls in a group, validate form values, and implement more advanced forms.
That is the ideal scenario for using Template Driven Forms, as the ngModel supports bidirectional data binding, just like the AngularJs ng-model directive. But other than that, Reactive Forms are a much better choice.
This should work as well:
<button #button (ngSubmit)="button.disabled = true" type="submit">Submit<button>
or just (click)
instead of (ngSubmit)
update (see comments)
<button #button [disabled]="!form.valid || button.hasAttribute('is-disabled')"
(ngSubmit)="button.setAttribute('is-disabled', 'true')"
type="submit">Submit<button>
update (use a directive)
@Directive({
selector: 'button[type=submit]'
})
class PreventDoubleSubmit {
@HostBinding() disabled:boolean = false;
@Input() valid:boolean = true;
@HostListener('click')
onClick() {
if(!valid) {
return;
}
this.disabled = true;
}
}
and use it like
<button type="submit" [valid]="!form.valid">Submit<button>
You need to add it to the directives: [PreventDoubleSubmit]
of the components where you want to use it or alternatively provide it globally
provide(PLATFORM_DIRECTIVES, {useValue: [PreventDoubleSubmit], multi: true})
Dealing with double-submission is easy to do wrong. On a form with <form #theForm="ngForm" (ngSubmit)="submit()">
:
<button type="submit" [disabled]="theForm.submitted" />
would only work if no validation whatsoever was present. Angular's ngForm.submitted is set to true when the button is pressed, not after the form passes validation. (NgForm's "submitted" property actually means "tried to submit".)
<button type="submit" [disabled]="theForm.submitted && theForm.valid" />
isn't much better: after getting validation errors on submission, the moment the user fixes the validation errors, the submit button disables itself right as they're reaching for it to re-submit.
Resetting ngForm.submitted
either directly or via ngForm.resetForm()
within your component's submit()
is a poor option, since submitted
is your primary variable controlling whether and where the validation error messages are displayed.
The real problem: Angular has no way to know when or whether your API calls in submit()
failed or succeeded. Even if Angular provided a property that meant "just clicked Submit button and it also passed all validation" on which you can hang [disabled]="thatProperty", Angular wouldn't know when to set the property back, such as when your API call errors out and you'd like to let the user press submit again to re-try the server.
Perhaps Angular might proscribe all submit functions to be of the form () => Observable<boolean>
and it could subscribe to your submit's success or failure, but it seems overkill just to reset a boolean in the framework.
So you must take action after all your API calls are finished and inform Angular somehow that the submit button is ready for reuse. That action is either going to be setting the explicit boolean you are already doing, or imperatively disabling.
Here's how to do it imperatively, without the boolean.
Add a template reference variable like #submitBtn to the submit button:
<button type="submit" #submitBtn class="green">Go!</button>
Pass it to your component's submit()
:
<form (ngSubmit)="submit(submitBtn)" ...>
Accept and use it component-side:
submit(submitBtn: HTMLButtonElement): void {
submitBtn.disabled = true;
/// API calls
submitBtn.disabled = false;
}
And if your API calls have multiple pathways that share a common error-handler, you'd need to pass the HTMLButtonElement on through to them as well, since they can no longer pluck it out of the component with this.disableButton
.
(Alternately, instead of declaring and passing #submitBtn, you already have #theForm declared, so pass that instead as :NgForm, and component code can drill-down to the button... or to an overlay over the whole form, or whatever.)
Whether this solution is more or less elegant than declaring another boolean that works slightly differently than ngForm.submitted
is opinion, but it is fact that Angular can't know when the component's submit() and all its async processes are finished without a subscription.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With