1

Starting with the following setup for a control value accessor:

@Component({
    selector: 'some-datepicker',
    template: `
      <input
        [attr.name]="formControlName"
        [matDatepicker]="picker"
        [value]="inputValue"
        (blur)="onBlur($event)
      />
      <mat-datepicker-toggle [for]="picker"></mat-datepicker-toggle>  
      <mat-datepicker #picker></mat-datepicker>  
      <div *ngIf="errors">
        <ul>
          <li *ngFor="let error of errors">{{ error.text }}</li>
        </ul>
      </div>
    `
})
export class DatepickerComponent implements ControlValueAccessor, OnInit {

    @Input formControlName: string;

    // would like to handle this in the component instead of 
    //  passing this down from the parent component
    @Input errors: { code: string; text: string; }[]

    // ... left out for brevity

    constructor(@Self() public ngControl: NgControl) {
      this.ngControl.valueAccessor = this;
    }

    ngOnInit(): void {}

    onBlur(event): void {

        // here it is still empty, because the validation has not run yet
        console.log('onBlur control.errors: ', this.ngControl.control?.errors);

        this.onChangeCallback(event.target.value);
        this.writeValue(event.target.value);
    }

    // ... left out. for brevity

    writeValue(value: string): void {
      this.value = value;

      if (value) {
        this.inputDate = new Date(value);
      }
    }
}

The form control is defined in a parent component. So I could pass the errors as @Input into this 'some-datepicker' component. But is there also a way that I don't have to do this, but instead integrate it into this component itself?

--- update

The stackblitz: https://stackblitz.com/edit/angular-ivy-5jnylk?file=src/app/app.component.ts

In there you will find the error handling to reside in the surrounding app component. What I am trying to achieve is that in the app component you can set validators.

But the following error handling should move to the custom component:

  ngOnInit(): void {
    // this logic could move to the custom component
    this.myForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
      this.myControlErrors$.next(
        this.mapValidationErrors(this.myForm.controls.someControl.errors)
      );
    });
  }
Remi
  • 4,663
  • 11
  • 49
  • 84
  • you need add as provider: NG_VALIDATORS,, see, e.g. this [SO](https://stackoverflow.com/questions/54645878/is-it-possible-to-crate-a-validator-for-a-custom-component-not-for-a-formcontro/54648891#54648891) – Eliseo Jan 31 '22 at 19:49
  • Aha, I see. With that can I keep the validators just in tact and just act upon form errors and re-render the template with errors? I'll give it a try. – Remi Feb 02 '22 at 07:54

1 Answers1

1

You shouldn't have to dynamically pass a formControlName trough input when already implementing ControlValueAcessor, you already have access to the parent control as an AbstractControl through ngControl.control, you can get attributes from it. Also your FormControl is not binded to the actual input, which seems to be causing the side effect of your input receiving values from changes in the FormControl but not emitting them (if you share more code or an StackBlitz example we can look at the whole picture).

This is a working example of what I think you're trying to do: https://stackblitz.com/edit/angular-ivy-5uefv4

Check the datepicker.component.ts file, the log from onBlur should be working now. Also I should disclose that I injected NgControl on the child component slightly different than you, but the way you did seems fine too.

Carlos Chaves
  • 51
  • 2
  • 4
  • Nice tip on the different way to inject the NgControl. I will update my question with additional info and a stackblitz example. – Remi Feb 02 '22 at 07:45