How to check for changes in form controls using Angular5

15,083

Solution 1

try using valueChanges along with Array.some() functions

hasError: boolean = false;

constructor(private elRef: ElementRef, 
            private formBuilder: FormBuilder, 
            private cdRef: ChangeDetectorRef) {     
  this.createForm(); 

  // listen to all changes on the form
  this.contratoForm.valueChanges.subscribe(_ => {
    const controllersToCheck = [
      'txtCodigoContrato', 
      'txtCodigoContrato2', 
      'txtContrato'
    ];
    this.hasError = controllersToCheck.some(ctrlName => {
      let ctrl = this.contratoForm.get(ctrlName);
      return (ctrl.dirty || ctrl.touched) &&
             (ctrl.hasError('required') || ctrl.hasError('hasWhites'));
    });
  });          
}

(checking !ctrl.valid is redundant because if controller has an error, then it is invalid)

HTML:

<div *ngIf="hasError">
  <span>check the fields in red, they are required</span>    
</div>

UPDATE

As stated here

You can also listen to {@link AbstractControl#statusChanges statusChanges} to be notified when the validation status is re-calculated.

I think it will suit your case more, so try to subscribe to statusChanges event (instead of valueChanges):

this.contratoForm.statusChanges.subscribe( ..........

UPDATE 2

use a function in your HTML:

<div *ngIf="hasErrors()">
  <span>check the fields in red, they are required</span>
</div>

add this function to your component ts file:

hasErrors() {
  const controllersToCheck = [
    'txtCodigoContrato', 
    'txtCodigoContrato2', 
    'txtContrato'
  ];
  return controllersToCheck.some(ctrlName => {
    let ctrl = this.contratoForm.get(ctrlName);
    return (ctrl.dirty || ctrl.touched) && 
           (ctrl.hasError('required') || ctrl.hasError('hasWhites'));
  });
}

I created a STACKBLITZ

if you add new controllers with the same validation logic, just add their control names to controllersToCheck array

Solution 2

You can do something like

this.contratoForm.valueChanges.subscribe(value = > //do something)

If you want you can add a map function before to get just the values from the form you care about that are changing

this.contratoForm.valueChanges
.map(values => values.txtCodigoContrato)
.subscribe(txtCodigoContrato = > //do something)
Share:
15,083
ararb78
Author by

ararb78

I like sports, but not from an armchair. I like the world of work schedule programming, the rest of the day I dedicate to cultivating my health and mind. I respect the environment, very aware of ecology and respect for animals.

Updated on June 15, 2022

Comments

  • ararb78
    ararb78 almost 2 years

    Im using reactive forms, I have multiple inputs, I want display a single error for de three inputs, the validatons are; fields are required and blank spaces should not be entered.

    component.html:

        <form [formGroup]="contratoForm" class="campo-form">
            <div class="campoFormulario">
                <label class="etiquetaFormulario" for="txtCodigoContrato">Código contrato</label>
                <div style="display:inline-block; position: relative;"  class="control">
                    <input [formControl]="txtCodigoContrato" type="text" id="txtCodigoContrato" name="txtCodigoContrato" class="cajaTexto ancho80" />
                    <span>/</span>
                </div>                
                <div style="display:inline-block; position: relative;" class="control">                
                    <input [formControl]="txtCodigoContrato2" type="text" id="txtCodigoContrato2" name="txtCodigoContrato2" class="cajaTexto ancho80" />              
                </div>
            </div>
            <div class="campoFormulario">
                <label class="etiquetaFormulario" for="txtContrato">Contrato</label>
                <div style="display:inline-block; position: relative;" class="control">
                    <input [formControl]="txtContrato" type="text" id="txtContrato" name="txtContrato" class="cajaTexto ancho350" />
    
                </div>
            </div>
             <div *ngIf="((!txtCodigoContrato.valid && (txtCodigoContrato.dirty || txtCodigoContrato.touched)) && (txtCodigoContrato.hasError('required') || txtCodigoContrato.hasError('hasWhites')))
                    ||
                    ((!txtCodigoContrato2.valid && (txtCodigoContrato2.dirty || txtCodigoContrato2.touched)) && (txtCodigoContrato2.hasError('required') || txtCodigoContrato2.hasError('hasWhites')))
                    ||
             ((!txtContrato.valid && (txtContrato.dirty || txtContrato.touched)) && (txtContrato.hasError('required') || txtContrato.hasError('hasWhites')))
                    ">
    
                <span>check the fields in red, they are required</span>    
            </div>              
            <div class="buttons">
                <button class="btn btn-primary boton" type="button" style="float:right;" (click)="onCloseLink()">
                    <span class="glyphicon glyphicon-off"></span> Cancel
                </button>
    
                <button class="btn btn-primary boton" type="button" style="float:right;" (click)="limpiar()">
                    <span class="glyphicon glyphicon-trash"></span> Clean
                </button>
                <button type="submit" [disabled]="!contratoForm.valid" class="btn btn-primary boton" style="float:right;" (click)="onClick()">
                    <span class="glyphicon glyphicon-floppy-disk"></span> Save
                </button>
            </div>
        </form>
    

    component.ts:

        import { Component, HostBinding, OnDestroy, OnInit, Input, Output, EventEmitter, ElementRef, NgModule, ViewChild, ChangeDetectorRef  } from '@angular/core';
        import { Validators, FormBuilder, FormControl, FormGroup, AbstractControl} from '@angular/forms';
        import { AfterViewChecked } from '@angular/core/src/metadata/lifecycle_hooks';
    
        @Component({
            selector: 'app-formulario-contrato',
            templateUrl: './contrato.component.html',
            styleUrls: ['./contrato.component.css'] 
        })
        export class FormularioContratoComponent  {
    
            contratoForm: FormGroup;  
    
            constructor(private elRef: ElementRef, private formBuilder: FormBuilder, private cdRef: ChangeDetectorRef) {     
                this.createForm();           
            }
    
            txtCodigoContrato = new FormControl('', [
                Validators.required
                ,this.hasNotWhites
            ]);
            txtCodigoContrato2 = new FormControl('', [
                Validators.required
                , this.hasNotWhites
            ]);
            txtContrato = new FormControl('', [
                Validators.required
                , this.hasNotWhites
            ]);
    
            createForm() {
                this.contratoForm = this.formBuilder.group({
                    txtCodigoContrato: this.txtCodigoContrato
                    ,txtCodigoContrato2: this.txtCodigoContrato2
                    ,txtContrato: this.txtContrato           
                });
            }  
    
            hasNotWhites(fieldControl: FormControl) {
                if (fieldControl.value.trim() != '') {
                    return null
                }
                else {
                   return { hasWhites: true };
                }          
            }
    
            ngAfterViewChecked() {
                this.cdRef.detectChanges();
            }
    
            limpiar() {   
            }
    
            onClick() {
                return null;
            }
    

    component.css:

    /* some stuff...*/
    :host /deep/ .control input.ng-invalid.ng-touched {
      border-color: #ff8080;
    }
    

    The validations work correctly, that is, they jump when the field stays empty or when I enter blank spaces. My problem is that I have to add more form controls and the if it contains the message can be made illegible:

      <div *ngIf="((!txtCodigoContrato.valid && (txtCodigoContrato.dirty || 
      txtCodigoContrato.touched)) && (txtCodigoContrato.hasError('required') || 
      txtCodigoContrato.hasError('hasWhites')))
                    ||
                    ((!txtCodigoContrato2.valid && (txtCodigoContrato2.dirty || 
      txtCodigoContrato2.touched)) && (txtCodigoContrato2.hasError('required') || 
      txtCodigoContrato2.hasError('hasWhites')))
                    ||
             ((!txtContrato.valid && (txtContrato.dirty || txtContrato.touched)) 
      && (txtContrato.hasError('required') || txtContrato.hasError('hasWhites')))
                    ">
    

    Is there any way to control these values by typescript, that is, each time a value in the control changes it is controlled by a function in typeScript that returns true or false and if only ask for the value of that function in my div?

    • ararb78
      ararb78 over 6 years
      this works but only jumps when I make a change in the input. I need to also jump when I just move with the cursor from one input to another without typing anything
  • ararb78
    ararb78 over 6 years
    It works but only jumps when I make a change in the input, I need to jump when I just move with the cursor from one input to another without typing anything
  • Andriy
    Andriy over 6 years
    try to subscribe to statusChanges event instead of valueChanges, see my updated answer
  • ararb78
    ararb78 over 6 years
    I changed it to "statuschange" and I was able to verify that when I move with the cursor from one field to another, the state of the field changes, but it does not jump in the function I do not understand why: this.contratoForm.statusChanges.suscribe(.... .
  • ararb78
    ararb78 over 6 years
    "statusChanges" evaluates the value "valid" or "invalid" and initially all inputs have the value invalid, so when I move from one to another with the cursor they are still invalids and do not enter the function.
  • ararb78
    ararb78 over 6 years
    Then, I think I need to check the touched status of the controls in my form.
  • Andriy
    Andriy over 6 years
    Please check my UPDATE #2 in the answer and see STACKBLITZ (stackblitz.com/edit/angular-ab4tna?file=app%2Fapp.component‌​.ts)