Search code examples
angularangular-reactive-forms

Angular validators are ignored or not working


I use ReactiveForms. I have a login component where i use two input fields (email and password). This input fields are both coming form the same custom component 'TextFieldComponent'. I try to add a specific regex validator on my email input and a minLength validator on my password input (just for testing). When I enter a valid email address I obtain my custom error message 'Invalid email address', which should not be the case. Also for the password input, the minLength validator is not taken into account.

LoginComponent TS:

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../auth.service';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { LoginRequest } from 'src/app/shared/models/login-request';
import { Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';

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

  loginForm: FormGroup;

  ngOnInit(): void {
    this.createLoginForm();
  }

  createLoginForm(): void {
    this.loginForm = new FormGroup({
      email: new FormControl(null, [Validators.required, Validators.pattern(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/)]),
      password: new FormControl(null, [Validators.required, Validators.minLength(4)])
    });
  }
}

LoginComponent Template

<form (ngSubmit)="onSubmit()" [formGroup]="loginForm" class="form-signin">
    <h1 class="h3 mb-3 font-weight-normal text-center">Please sign in</h1>

    <app-text-input
        [formControlName]="'email'"
        [label]="'Email address'">
    </app-text-input>

    <app-text-input
        [formControlName]="'password'"
        [label]="'Password'"
        [type]="'password'">
    </app-text-input>

    <app-button [disabled]="loginForm.invalid" type="submit" label="Sign in" className="btn btn-primary w-100" class="w-100"></app-button>
</form>

TextFieldComponent TS

import { Component, ElementRef, Input, Self, ViewChild } from '@angular/core';
import { AbstractControl, NgControl } from '@angular/forms';

@Component({
  selector: 'app-text-input',
  templateUrl: './text-input.component.html',
  styleUrls: ['./text-input.component.scss']
})
export class TextInputComponent {
  @ViewChild('input', {static : true}) input: ElementRef; // reference to the input field
  @Input() type : 'email' | 'text' | 'password' = 'text'; // input type (password, text, date, button, ...)
  @Input() label: string; // Label that describes the input field

  constructor(
    @Self() public controlDir: NgControl
  ) {
    // Bind the valueAccessor to this particular class, which lets us access the control directive inside our component + template
    this.controlDir.valueAccessor = this;
  }

  ngOnInit(): void {
    const control = this.controlDir.control as AbstractControl;
    control.updateValueAndValidity();
  }

  onChange(event? : any) { }

  onTouched(event? : any) {}

  writeValue(obj: any): void {
    this.input.nativeElement.value = obj || '';
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
}

TextFieldComponent Template

<div class="form-label-group">

<label for="{{label}}">{{label}}</label>
<input #input [ngClass]="(controlDir && controlDir.control && controlDir.control.touched) 
        ? !controlDir.control.valid 
        ? 'is-invalid' 
        : 'is-valid' 
        : null" [type]="type" (input)="onChange($event)" (blur)="onTouched()" id="{{label}}"
    class="form-control" placeholder="{{label}}" autocomplete="off" >

<!-- Synchornous Error Messages -->

<!-- Invalid control check -->
<div *ngIf="(
    controlDir && 
    controlDir.control &&
    !controlDir.control.valid &&
    controlDir.control.touched
    )" class="invalid-feedback">

    <!-- Required control check -->
    <span *ngIf="controlDir.control.errors?.['required']">{{label}} is required</span>

    <!-- Pattern control check -->
    <div *ngIf="controlDir.control.errors?.['pattern']">Invalid Email Format</div>


</div>

Anyone knows what is wrong with my validators and why they are not working as expected?


Solution

  • The problem is the custom form control.

    You have:

    <input #input ... (input)="OnChange($event)">
    

    $event is a input event, you should use

    <input #input ... (input)="onChange(input.value)">
    

    or create a function

      change(event:any)
      {
        this.onChange(event.target.value)
      }
      //and use
       <input #input ... (input)="change($event)">
    

    TIP: sometimes, to check a FormGroup is util write in the .html (in this case in your loginComponent)

    <pre>
      {{loginForm.value|json}}
    </pre>
    

    NOTE: Generally a custom form control it's use to make "complex UI". You can create a component with label, errors... using viewProviders like, e.g. this SO