How can I manually set an Angular form field as invalid?

I am working on a login form and if the user enters invalid credentials we want to mark both the email and password fields as invalid and display a message that says the login failed. How do I go about setting these fields to be invalid from an observable callback?

Template:

<form #loginForm="ngForm" (ngSubmit)="login(loginForm)" id="loginForm">
  <div class="login-content" fxLayout="column" fxLayoutAlign="start stretch">
    <md-input-container>
      <input mdInput placeholder="Email" type="email" name="email" required [(ngModel)]="email">
    </md-input-container>
    <md-input-container>
      <input mdInput placeholder="Password" type="password" name="password" required [(ngModel)]="password">
    </md-input-container>
    <p class='error' *ngIf='loginFailed'>The email address or password is invalid.</p>
    <div class="extra-options" fxLayout="row" fxLayoutAlign="space-between center">
     <md-checkbox class="remember-me">Remember Me</md-checkbox>
      <a class="forgot-password" routerLink='/forgot-password'>Forgot Password?</a>
    </div>
    <button class="login-button" md-raised-button [disabled]="!loginForm.valid">SIGN IN</button>
     <p class="note">Don't have an account?<br/> <a [routerLink]="['/register']">Click here to create one</a></p>
   </div>
 </form>

Login method:

 @ViewChild('loginForm') loginForm: HTMLFormElement;

 private login(formData: any): void {
    this.authService.login(formData).subscribe(res => {
      alert(`Congrats, you have logged in. We don't have anywhere to send you right now though, but congrats regardless!`);
    }, error => {
      this.loginFailed = true; // This displays the error message, I don't really like this, but that's another issue.
      this.loginForm.controls.email.invalid = true;
      this.loginForm.controls.password.invalid = true; 
    });
  }

In addition to setting the inputs invalid flag to true I've tried setting the email.valid flag to false, and setting the loginForm.invalid to true as well. None of these cause the inputs to display their invalid state.


in component:

formData.form.controls['email'].setErrors({'incorrect': true});

and in HTML:

<input mdInput placeholder="Email" type="email" name="email" required [(ngModel)]="email"  #email="ngModel">
<div *ngIf="!email.valid">{{email.errors| json}}</div>

Adding to Julia Passynkova's answer

To set validation error in component:

formData.form.controls['email'].setErrors({'incorrect': true});

To unset validation error in component:

formData.form.controls['email'].setErrors(null);

Be careful with unsetting the errors using null as this will overwrite all errors. If you want to keep some around you may have to check for the existence of other errors first:

if (isIncorrectOnlyError){
   formData.form.controls['email'].setErrors(null);
}

In new version of material 2 which its control name starts with mat prefix setErrors() doesn't work, instead Juila's answer can be changed to:

formData.form.controls['email'].markAsTouched();

I was trying to call setErrors() inside a ngModelChange handler in a template form. It did not work until I waited one tick with setTimeout():

template:

<input type="password" [(ngModel)]="user.password" class="form-control" 
 id="password" name="password" required (ngModelChange)="checkPasswords()">

<input type="password" [(ngModel)]="pwConfirm" class="form-control"
 id="pwConfirm" name="pwConfirm" required (ngModelChange)="checkPasswords()"
 #pwConfirmModel="ngModel">

<div [hidden]="pwConfirmModel.valid || pwConfirmModel.pristine" class="alert-danger">
   Passwords do not match
</div>

component:

@ViewChild('pwConfirmModel') pwConfirmModel: NgModel;

checkPasswords() {
  if (this.pwConfirm.length >= this.user.password.length &&
      this.pwConfirm !== this.user.password) {
    console.log('passwords do not match');
    // setErrors() must be called after change detection runs
    setTimeout(() => this.pwConfirmModel.control.setErrors({'nomatch': true}) );
  } else {
    // to clear the error, we don't have to wait
    this.pwConfirmModel.control.setErrors(null);
  }
}

Gotchas like this are making me prefer reactive forms.


In my Reactive form, I needed to mark a field as invalid if another field was checked. In ng version 7 I did the following:

    const checkboxField = this.form.get('<name of field>');
    const dropDownField = this.form.get('<name of field>');

    this.checkboxField$ = checkboxField.valueChanges
        .subscribe((checked: boolean) => {
            if(checked) {
                dropDownField.setValidators(Validators.required);
                dropDownField.setErrors({ required: true });
                dropDownField.markAsDirty();
            } else {
                dropDownField.clearValidators();
                dropDownField.markAsPristine();
            }
        });

So above, when I check the box it sets the dropdown as required and marks it as dirty. If you don't mark as such it then it won't be invalid (in error) until you try to submit the form or interact with it.

If the checkbox is set to false (unchecked) then we clear the required validator on the dropdown and reset it to a pristine state.

Also - remember to unsubscribe from monitoring field changes!