In Angular one of most common ways to validate forms is to disable the submit button. It’s a nice way (alongside with helpful messages) to indicate the user that something is not right in the form.

So to disable the button we use something like this

...

<button type="submit" [disabled]="form.invalid">SEND</button>

...

<button type="submit" [disabled]="!form.valid">SEND</button>

So above there are two most common ways to disable button when the form is not valid. Have you ever thought about the difference between form.invalid and !form.valid ? Although logically they must be the same, but actually there is a slight difference between them. To demonstrate this we will create a simple form with one input that checks if given email exists in database. For that we will use Reactive Forms and a custom async validator.

<form [formGroup]="form"> <input placeholder="Enter your email" formControlName="email"/> <button type="submit" [disabled]="form.invalid">SEND</button> <div *ngIf="form.get('email').pending">

<img src="loader.gif" >

</div>

</form>

So the markup is very simple, we have a form and one input inside of it. We also have a button to submit the form which is disabled with form.invalid .

We also have a div to display a simple loading spinner while we will check the existence of given email in database.

In a .ts file we’ve used FormBuilder to create our form. We applied required and email validators from Angular and one custom async validator to check the email.

form: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() {

this.form = this.fb.group({

email: ['', [Validators.required, Validators.email],

this.checkValidEmail]

})

}

So here this.checkValidEmail is our custom validator which is just a function. In real applications you might need to have a service, to send a request to database, check if the email exists and return the result. To keep things simple we will emulate that call to backend with setTimeout() . The important part is that calling the backend (or emulating it with setTimeout() ) is an asynchronous operation so we can’t use standard validators or custom synchronous validators.

So our checkValidEmail function will compare given email from input field with a static email. If they match it means email is busy and we need to inform the user to choose another email and also we need to disable the button.

checkValidEmail(control: AbstractControl) {

return new Promise((resolve, reject) => {

setTimeout(() => {

if (control.value === 'super@secret.com') {

resolve({ emailIsTaken: true })

} else {resolve(null)}

}, 2000)

})

}

So now we are ready. Let’s take a look at browser

So initially the button was disabled, but when we inserted the email the button became enabled and after 2 seconds (time set on setTimeout() ) the button became disabled again because that email was indeed in “database”.

The reason why for a short time button was enabled is that there is one more state except valid and invalid . And that is pending state. Initially pending is set to false, but when we use async validator pending becomes true and valid and invalid become false.

When our async validator starts working the state changes to pending and invalid becomes false which means that the form is valid. This is why when using form.invalid with async validator we get this behaviour. Imagine the operation might take 3 or even more seconds and while it’s pending the button will be enabled and users can click and submit the form.

Situation is different when we use !form.valid instead of form.invalid . Because in a pending state valid becomes false the button will be disabled until we get result and depending on that result we either enable the button or not.

So hopefully now it’s clear why using [disabled]=”!form.valid” is more secure than [disabled]=”form.invalid” .

Thanks for reading. The full code is available here