0

Edit (May, 4 2017):

After a lot of research, it's clear to me that this is not currently possible in a "native" way. See here: https://github.com/angular/angular/issues/7113

Original Post:

Currently, the following code allows me to display a validation error when the user clicks the submit button without entering a valid email into the input field:

import {
  Component
} from '@angular/core';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent {
  submitted = false;

  public submitForm(form: any): void {
    this.submitted = true;
    console.log(form);
  }
}
.invalid-message {
  color: yellow;
}
<form id="loginForm" #loginForm="ngForm" (ngSubmit)="submitForm(loginForm.value)">
  <div class="form-group">
    <input type="email" class="form-control form-control-lg" #email="ngModel" name="email" ngModel required pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" placeholder="email">
    <ng-container *ngIf="this.submitted && email.errors">
      <small [hidden]="!email.errors.required" class="invalid-message float-right">EMAIL REQUIRED</small>
      <small [hidden]="!email.errors.pattern" class="invalid-message float-right">INCORRECT EMAIL FORMAT</small>
    </ng-container>
  </div>
  <button type="submit" form="loginForm" class="btn btn-secondary btn-block btn-lg">LOGIN</button>
</form>

What I would like to have is the validation error message disappearing automatically when the user starts typing again anything into the input. This is the Airbnb behavior for example, that you can experience if you try to login on the web.

Sammy
  • 3,395
  • 7
  • 49
  • 95

2 Answers2

1

Have a look at the Reactive Forms module.

Its pretty much designed for this sort of thing, it exposes focus, dirty, touched and various other useful properties.

Instead of using using ngModel - you should bind each input to a [formControl]

Then in your component. You use formBuilder to construct each control.

constructor(fb : FormBuilder) {
    this.form = fb.group({
      'firstName' : ['', Validators.required],
      'lastName' : ['', Validators.maxLength(2)]
    })

Note that the form here is a FormGroup which exposes a get method which you can use to test each item in the group.

Each item is a FormControl - you can find all this out from the API.

e.g:

hasError(n: string){
  return this.form.get(n).hasError('required');
}

If you don't want to display the error message when the user is typing you can check for !focus in your *ngIf

<div *ngIf="form.get('firstName').valid && !form.get('firstName').focus">
    Error Message
</div>

Edit: There's a lot to mention when it comes to ReactiveForms - too much to mention here. But you can check out some of these resources:

Official Angular2 ReactiveForms Guide

Thoughtram blog about reactiveForms

Daniel Cooke
  • 1,426
  • 12
  • 22
  • Thank you, my problem is that for a login form with two variables, using reactive forms seemed like an overkill for me. I was just looking for a `.focus` like behavior for template driven forms; apparently it doesn't exist! – Sammy May 03 '17 at 16:30
  • Its a fairly lightweight module - especially when you use DI form builder. Remember Angular2 is pretty much overkill for most things, you have to embrace it. – Daniel Cooke May 03 '17 at 16:31
  • Thank you, I think this is what I'm going to do after all. One more question please: is it advisable to create the Form Group controls using `FormBuilder` in the constructor or in `ngOnInit`? – Sammy May 03 '17 at 20:26
  • 1
    By the way, I re-imlemented the whole code using Reactive Forms, but `AbstractControl.focus` does not exist according to Angular's `FormGroup` APIs. – Sammy May 03 '17 at 21:23
0

What about adding a condition inside the ngIf of your errors container checking if the current input has focus currently?

Like add this to your ngIf:

ngIf="... && !elementHasFocus()"

And define the current function inside your controller.

function elementHasFocus(){
    // ensure you inject $element in your controller in order to access the current element which triggered the bind function
    return $element[0] === document.activeElement;
}
quirimmo
  • 9,800
  • 3
  • 30
  • 45
  • you need to inject $element to the controller then access the $element inside the function. It will take the current element. This is what I meant with this. Adding explanation in the answer – quirimmo May 03 '17 at 16:53