0

When working with forms in angular a normal approach is to just have the form elements (like inputs) directly in the form

<form>
 <input>
</form>

When this form is submitted and the input has a validator eg. required. the form inputs are validated and if it's not valid we can show that to the user... great...

To be able to reuse custom inputs we can create a component that contains this input + extras.

<form>
 <custom-component>
</form>

Check this stackblitz: https://stackblitz.com/edit/components-issue-whxtth?file=src%2Fapp%2Fuser%2Fuser.component.html

When hitting the submit button only one input is validated. If you interact with both inputs they will validate

There is nothing strange as far as I can see in the CVA component.

@Component({
  selector: "user",
  templateUrl: "./user.component.html",
  styleUrls: ["./user.component.scss"],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent implements ControlValueAccessor {
  constructor(@Optional() @Self() public ngControl: NgControl) {
    if (this.ngControl != null) {
      // Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
      this.ngControl.valueAccessor = this;
    }
  }

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

  writeValue(val: string): void {}

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

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
}
2
  • Material's input doesn't have changeDetection: ChangeDetectionStrategy.OnPush if you remove it will work as expected. Commented Nov 5, 2020 at 11:00
  • @Eldar I guess that will "work" but is that really the solution? should not the reactive form handle this use case? Commented Nov 5, 2020 at 13:58

1 Answer 1

2

Like I said the main reason why your input doesn't react to the validation is your component configured as changeDetection: ChangeDetectionStrategy.OnPush this will make your component only react to restricted actions :

  1. The Input reference changes
  2. An event originated from the component or one of its children
  3. Use the async pipe in the view
  4. Run change detection explicitly (Will provide a workaround)

So you have 2 options :

  1. Embrace the default change detection strategy (Like Angular Material did)
  2. Pick an ugly workaround.

Here how that workaround is :

constructor(
    @Optional() @Self() public ngControl: NgControl,
    private cdr: ChangeDetectorRef // import the CDR
  ) {
    if (this.ngControl != null) {
      this.ngControl.valueAccessor = this;
      const { _parent } = this.ngControl as any; // this is not a public property
      // and refers to the parent form
      _parent.ngSubmit.subscribe((r: any) => { // this is fired when the form submitted
        this.cdr.detectChanges(); // detect changes manually
      });
    }
}

Stackblitz

This will work where you have a form as the parent. With some null checks, it will work consistently. But you may encounter some other scenarios where you may not have the opportunity to trigger manual change detection and it will hurt the reusability of your component badly.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.