3

<mat-error> in child ControlValueAccessor component has no effect of parent formControl validations.

<!-- Parent component template -->
<app-unique-name-text-box [formControl]="categoryName"></app-unique-name-text-box>
// Parent Component ts
this.addCategoryForm = fb.group({
      'categoryName': ['', Validators.compose([Validators.required, BlankSpaceValidator.validate])]
});
this.categoryName = this.addCategoryForm.controls['categoryName'];
<!-- Child component template -->
<mat-form-field class="example-full-width">
    <input matInput placeholder="Name" [formControl]="uniqueNameControl" (blur)="onInputBlur()">
    <mat-error *ngIf="errorState">Enter a unique name</mat-error>
</mat-form-field>
// Child component ts
import {Component, OnInit, Optional, Self} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, NgControl} from '@angular/forms';
import {distinctUntilChanged} from "rxjs/operators";

@Component({
  selector: 'app-unique-name-text-box',
  templateUrl: './unique-name-text-box.component.html',
  styleUrls: ['./unique-name-text-box.component.scss']
})
export class UniqueNameTextBoxComponent implements OnInit, ControlValueAccessor {
  uniqueNameControl: FormControl = new FormControl('');

  onChanged: any = () => {};
  onTouched: any = () => {};

  constructor(@Optional() @Self() public ngControl: NgControl) {
    if (this.ngControl != null) {
      this.ngControl.valueAccessor = this;
    }
    this.uniqueNameControl.valueChanges.pipe(distinctUntilChanged()).subscribe(
      val => this.setInputValue(val)
    );
  }

  get errorState() {
    return this.ngControl.errors !== null && !!this.ngControl.touched;
  }

  ngOnInit() {
  }

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

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

  writeValue(obj: any = ''): void {
    this.uniqueNameControl.patchValue(obj);
  }

  setInputValue(val = '') {
    this.uniqueNameControl.patchValue(val);
    this.onChanged(val);
  }
}

Do I need to add some extra configuration here to make <mat-error> to display on parent formControl invalid

Gourav Pokharkar
  • 1,568
  • 1
  • 11
  • 33
  • Your child component is not a control value accessor if it doesn't register itself as such with `providers`. Google for "ControlValueAccessor example" to find an example, and look for the `providers` declaration in the Component decorator. – JB Nizet Jan 11 '20 at 10:22
  • 2
    I'm following this approach: https://material.angular.io/guide/creating-a-custom-form-field-control#-code-ngcontrol-code-, otherwise I'm getting `cyclic dependency error` – Gourav Pokharkar Jan 11 '20 at 10:24
  • I think that the better aproach is using a custom errorStateMatcher. The idea is that the inner mat-input is incorrect if the ngControl is incorrect, see https://stackoverflow.com/questions/58459617/component-for-wrap-angular-material-input-does-not-show-error-styles/58472470#58472470. NOTE: In the stackblitz I add a `@Input` error to control the error of the formGroup – Eliseo Jan 11 '20 at 22:43
  • @GeniusGo from the link that you shared. It actually just says 'if direct access to the control needed - remove ref forwarding from providers' – simply good Jun 14 '21 at 21:14

2 Answers2

7

You have to add parent control validator manually to child component like this

Try this:

 ngOnInit() {
    const validators = this.ngControl.control.validator;
    this.uniqueNameControl.setValidators(validators ? validators : null);
    this.uniqueNameControl.updateValueAndValidity();
 }

Example

Chellappan வ
  • 23,645
  • 3
  • 29
  • 60
  • Ohh..so, I was not actually inheriting the validations through `ngControl`? :P – Gourav Pokharkar Jan 11 '20 at 11:05
  • In your child component you are creating new FormControl which does not have any validator by default. so It is overriding parent form control validator. – Chellappan வ Jan 11 '20 at 11:37
  • Ok. For `setErrors()` why I will need to set inherited errors on it after `this.uniqueNameControl.updateValueAndValidity();`. If I don't do so, it doesn't work. Is it right way to inherit validators and errors from parent `formControl`? – Gourav Pokharkar Jan 11 '20 at 11:39
  • 1
    When you add validator on the fly you have to manually call updateValueandValidity of formControl in order to formControl update the value and validity properly. – Chellappan வ Jan 11 '20 at 11:40
  • But, if I call `setErrors()` method before calling `updateValueAndValidity()`, it's removing the errors which I specify in `setErrors()` from child `formControl`. – Gourav Pokharkar Jan 11 '20 at 11:43
  • Did you check the stackblitz? – Chellappan வ Jan 11 '20 at 11:47
  • Yes. I did check. It works as I wanted. But, the issue is I have to dynamically set custom errors on some event in parent component, which I indicate to child using `@Input()` and I have to set those errors using `setErrors()`. Our above discussion is regarding this issue too. – Gourav Pokharkar Jan 11 '20 at 11:51
  • Also, what is the use of `errorState` property (https://material.angular.io/guide/creating-a-custom-form-field-control#-code-errorstate-code-), if we have to set this validators manually. SORRY, but I'm first time implementing the CVA in Angular, so a lot confused. – Gourav Pokharkar Jan 11 '20 at 11:54
  • errorSate is FormField component property. I have not used that one check here for more details: https://material.angular.io/components/form-field/api#MatFormFieldControl – Chellappan வ Jan 11 '20 at 12:00
  • @Chellappanவ, what do you think about using a custom errorStateMatcher that take account the NgControl? – Eliseo Jan 11 '20 at 22:23
  • If there is no error in control how customerrorState matcher will take effect? – Chellappan வ Jan 12 '20 at 05:01
0
  ngAfterContentInit() {
    // Subscribe to changes in the child control state in order to update the form field UI.
    this.stateChanges.subscribe(() => {
      return (this.errorState = !this.ngControl.control.valid);
    });   }
  • Please add description to your answer – mechnicov May 19 '20 at 14:49
  • It's great to provide code that will solve the issue. But you should also provide a prosaic description that explains why it solves the issue. A simple description will suffice. – Display name May 19 '20 at 15:09
  • 2
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. [From Review](/review/low-quality-posts/26170629) – double-beep May 19 '20 at 18:52