JustDontKnow
JustDontKnow

Reputation: 351

Angular 6 Reactive forms - trigger validation on focus/blur

How to fire a validation message on blur/focus in/change ?

What I've tried:

import { FormGroup, Validators, FormBuilder, FormControlName, FormControl } from '@angular/forms';

// Component - Class implementation:

addressForm: FormGroup;

consructor(private fb: FormBuilder) { }

ngOnInit() {
    this.addressForm = this.fb.group({
      zip: ['', {
        validators: [Validators.required, Validators.minLength(4)],
        updateOn: 'change' //blur
      }] 
    });
}

But it doesn't work..

Upvotes: 4

Views: 4548

Answers (3)

DeborahK
DeborahK

Reputation: 60528

I watch for the blur event from the input elements on the form. Then I merge the valueChanges and blur observables like this:

  ngAfterViewInit(): void {
    // Watch for the blur event from any input element on the form.
    // This is required because the valueChanges does not provide notification on blur
    const controlBlurs: Observable<any>[] = this.formInputElements
      .map((formControl: ElementRef) => fromEvent(formControl.nativeElement, 'blur'));

    // Merge the blur event observable with the valueChanges observable
    // so we only need to subscribe once.
    merge(this.productForm.valueChanges, ...controlBlurs).pipe(
      debounceTime(800)
    ).subscribe(value => {
      this.displayMessage = this.genericValidator.processMessages(this.productForm);
    });
  }

I have a complete example here: https://github.com/DeborahK/Angular-ReactiveForms/tree/master/APM

Upvotes: 0

Aviad P.
Aviad P.

Reputation: 32639

I also had this problem, and I solved it - as I usually do - with a directive and a service. The directive gets attached to input elements, and the service coordinates the events which should trigger validation.

Here's the directive:

@Directive({
    selector: 'input, textarea, select'
})
export class TriggerValidationDirective {
    constructor(private svc: TriggerValidationService, private el: ElementRef) {
    }

    @HostListener('blur', ['$event'])
    @HostListener('selectionChange', ['$event'])
    @HostListener('change', ['$event'])
    onTriggerValidation(e) {
        this.svc.triggerValidation(this.el);
    }
}

And here's the service

@Injectable({ providedIn: 'root' })
export class TriggerValidationService {
    private triggerValidationSubject = new Subject<ElementRef | null>();

    triggerValidation$ = this.triggerValidationSubject.asObservable();

    triggerValidation(el?: ElementRef<any>) {
        this.triggerValidationSubject.next(el);
    }
}

And here's how I'd use it in a component which includes a reactive form:

@Component({
  // ... selector, etc.
  providers: [ { provide: TriggerValidationService, useClass: TriggerValidationService } ]
})
export class TheComponent implements OnInit, OnDestroy {

  constructor(private triggerValidationService: TriggerValidationService) {}

  ngOnInit() {
    this.sub = this.triggerValidationService.triggerValidation$.subscribe(_ => {
      // trigger validation, e.g. this->formGroup.updateValueAndValidity();
    });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }
}

A brief recap on how this works: The directive gets attached to all relevant input elements (text inputs, text areas, date pickers, selects, etc.) and listens to the appropriate event which we want to use to trigger validation. The directive then notifies the service which emits on an observable. The component hosting the form subscribes to that observable and performs validity checks whenever it emits.

Important: Note that the service has to be provided by the component hosting the form (note the providers section in the component's decorator), so that different forms don't trigger each other's validation.

Upvotes: 1

Kishan
Kishan

Reputation: 793

You can use template drive validation in your form rather than use reactive form validation. here is below example for the same :

<div class="group input-group input-group-sm has-feedback" [ngClass]="{'has-success' : contact_name.valid && contact_name.touched,'has-error' : contact_name.invalid && contact_name.touched}">
    <div class="font-icn input-group-addon">
        <i class="fa fa-calendar"></i>
    </div>
    <input type="number" min="0" class="input_k" name="contact_name" id="contact_name" [value]="contactForm.contact_name"
    [(ngModel)]="contactForm.contact_name" #contact_name="ngModel" >
    <label for="contact_name" [ngClass]="{'active' : contactForm.contact_name != ''  }">Enter Contact Form</label>
    <span aria-hidden="true" class="fa form-control-feedback" [ngClass]="{'fa-check': contact_name.valid && contact_name.touched,'fa-remove' : contact_name.invalid && contact_name.touched}"></span>
    <div class="help-block" *ngIf="contact_name.invalid && contact_name.touched">Please select contact form.</div>
</div>   

Upvotes: 0

Related Questions