2

Created Address Custom Form Controller and using in inside CustomerForm, while submitting the form without filling anything, getting mat error of required field for Customer Code and Customer Name, not getting any mat error of required field in Address1, Country, City, and Zipcode.

I had tried to add a custom validator which is showing a combined error of address. But it is not solving my problem as it is always showing error addresses until all required fields of an address are filled.

customer.component.html

<form (ngSubmit)=" custForm.valid && saveOrUpdateCustomer()" [formGroup]="custForm">

    <!-- Customer Code -->
     <mat-form-field>
        <input matInput placeholder="Customer Code" formControlName="custCode" required >
        <mat-error *ngIf="custForm.get('custCode').hasError('required')">
            *Required field
        </mat-error>
    </mat-form-field>

    <!-- Customer Name -->
    <mat-form-field>
        <input matInput placeholder="Customer Name" formControlName="custName" required>
        <mat-error *ngIf="custForm.get('custName').hasError('required')">
            *Required field
        </mat-error>
    </mat-form-field>

    <h6>Address Details</h6>
    <!--Address Detail-->
    <app-address formControlName="address" #address></app-address>
    <div *ngIf="custForm.controls['address'].hasError('isNotValid')">
    </div>
    <div>
        <button type="submit" >Submit</button>
    </div>
</form>

address.component.hml

<form [formGroup]="addressForm">

  <!-- Address 1 -->
  <mat-form-field>
    <input matInput placeholder="Address 1" formControlName="address1" required>
    <mat-error *ngIf="addressForm.get('address1').hasError('required')">
      *Required field
    </mat-error>
  </mat-form-field>
`
  <!-- Address 2 -->
  <mat-form-field>
    <input matInput placeholder="Address 2" formControlName="address2">
  </mat-form-field>

  <!-- Address 3 -->
  <mat-form-field>
    <input matInput placeholder="Address 3" formControlName="address3">
  </mat-form-field>

    <!-- Country -->
    <mat-form-field>
    <input matInput placeholder="Country" formControlName="country" required>
    <mat-error *ngIf="addressForm.get('country').hasError('required')">
      *Required field
    </mat-error>
  </mat-form-field>

    <!-- State -->
    <mat-form-field>
    <input matInput placeholder="State" formControlName="state">
  </mat-form-field>

    <!-- City -->
    <mat-form-field>
    <input matInput placeholder="City" formControlName="city" required>
    <mat-error *ngIf="addressForm.get('country').hasError('required')">
      *Required field
    </mat-error>
  </mat-form-field>

  <!-- Zip Code -->
  <mat-form-field>
    <input matInput placeholder="Zip Code">
    <mat-error *ngIf="addressForm.get('country').hasError('required')">
      *Required field
    </mat-error>
  </mat-form-field>

</form>

address.component.ts

import { Component, OnInit, forwardRef} from '@angular/core';
import { FormGroup, FormBuilder, Validators, ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, NG_VALIDATORS } from '@angular/forms';
import { Address } from 'app/model/address/address.interface';

@Component({
  selector: 'app-address',
  templateUrl: './address.component.html',
  styleUrls: ['./address.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: AddressComponent,
      multi: true
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => AddressComponent),
      multi: true,
    }
  ]
})
export class AddressComponent implements OnInit, ControlValueAccessor, Validators {

  public addressForm: FormGroup;
  private _address: any = {};
  istoched = false;

  constructor(
    private fb: FormBuilder,
  ) { }

  ngOnInit() {
    this.addressForm = this.fb.group({
      address1: ['', [Validators.required]],
      address2: [''],
      address3: [''],
      country: ['', [Validators.required]],
      state: [''],
      city: ['', [Validators.required]],
      zip: [''],
    });
  }

  propagateChange = (_: any) => { };

  writeValue(address: Address) {
    this._address = address;
    if (address) {
      this.addressForm.setValue({
        address1: address.address1,
        address2: address.address2,
        address3: address.address3,
        country: address.country,
        state: address.state,
        city: address.city,
        zip: address.zip,
      });
    }
  }

  registerOnChange(fn: any) {
    this.addressForm.valueChanges.subscribe(() => {
      fn(this.addressForm.value);
    });
  }

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

  public validate(formControl: FormControl) {
    let isValid = true;
    Object.keys(this.addressForm.controls).forEach((control) => {
      if (this.addressForm.controls[control] instanceof FormControl) {
        if (!(<FormControl>this.addressForm.controls[control]).valid) {
          isValid = false;
          if (this.istoched) {
            this.addressForm.controls[control].markAsTouched();
          }
        }
      }
    });
    this.istoched = true;
    if (isValid) {
      return null;
    } else {
      return {
        primaryContactError: {
          valid: false,
        }
      };
    }
  }
}

While clicking on the submit button of customer form, if any required field is not filled, it should show required error.

Nikhil Mishra
  • 397
  • 1
  • 6
  • 21
  • A mat-error only it's showed if the input is "touched". check this SO https://stackoverflow.com/questions/58160241/reactive-form-using-controlvalueaccessor-for-subform-show-errors-on-submit/58162905#58162905 to know how mark as touched – Eliseo Oct 02 '19 at 06:44

1 Answers1

2

I think you may have misunderstood how to use ControlValueAccessor. It works in conjunction with a FormControl object so that changes made programatically to the FormControl are passed to the component's view. You have implemented ControlValueAccessor in AddressComponent which therefore requires usage like <app-address [formControl]="..."> or <app-address formControlName="..."> but it appears that AddressComponent represents the FormGroup instead. So none of your ControlValueAccessor implementation will work as intended - and you may not need it at all.

Your validate() logic requires that a form control be invalid in order for it to be marked as touched, but if the control is not touched it will always be valid (default behavior). To change the default behavior, implement your own ErrorStateMatcher and apply it to your MatInput inputs. I don't see anything that registers or calls validate() - you need that to happen as well.

Also, there is no need to use *ngIf conditions with <mat-error> elements unless you have multiple <mat-error>s for different error types, or you want to hide errors for other error types. <mat-error> will only be displayed when the form control state is invalid.

G. Tranter
  • 16,766
  • 1
  • 48
  • 68