简体   繁体   中英

How to get the inner control in a ValueControlAccessor in the correct validation satet in Angular

We started with implementation of a component library for our Angular (version 12 at the moment) project.

At the moment we implement the first component as a blueprint. The component should encapsulate a Material input ( matInput ) with label, mat-errors, some validators etc. so that the developer just needs to write s.th. like

<our-input max-length="5" required label="fancy field" [formControlName]="fancyField">

to get a fully designed input field. Btw. I'm aware that mixing validations from FormControl and template should not be done, but that is only for the sake of the example; in real life the validation will be defined by the component like a datepicker.

At the moment our template is realy simple:

<mat-form-field class="example-full-width" appearance="fill">
  <input
    matInput
    [formControl]="formControl"
    (input)="onInputChange($event)"
    (blur)="onInputBlur()"
  />
  <mat-error *ngFor="let error of outerControl.errors | keyvalue">
    <ng-container [ngSwitch]="error.key">
      <ng-container *ngSwitchCase="'required'">
        Field required error
      </ng-container>
...
    </ng-container>
  </mat-error>
</mat-form-field>

We are stuck at the part with the validation. If the validation happens only for the "outer control" (the control fancyField in the example) the inner matInput neither get the styling nor not show the validation errors because it simply does not know that it is invalid.

Because we register some validators in a similar way Netanel Basal described in his blog post Adding Integrated Validation to Custom Form Controls in Angular we already have access to the outer NgControl :

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

We already tried to call the outer validators as an inner validator

 this.formControl.addValidators((control) => this.ngControl.control.validator?.(control));
 // the same for asyncValidators?

or inspired by Christian Lüdemann's post Form validation with ControlValueAccessor to assign the outer form control to the inner form control on view-init

public ngAfterViewInit(): void {
    // syncing with validators on host element
    this.formControl = this.ngControl.control as FormControl;
}

but both methods trigger the validators twice. Additionally I have a strange feeling about connecting a FormControl to two template controls.

Here is an example at StckBlitz https://stackblitz.com/edit/angular-ivy-swiydl

I guess there some other solutions, too.

I already thought about trying to mirror the outer state but did not find any feasible way to have change detection for properties of an object which is not in a template. The only solution I could found was to create an inner component to bind properties of this.ngControl.control but the felt strange, too.

Which way would you got or is there any better way?

Since you are injecting NgControl it's your job to set up the NgControl correctly with the right value accessor and validators. You've set up the value accessor correctly. For the latter part you can do something like:

ngOnInit(): void {
   const control = this.ngControl.control;
   const validators = control.validator
      ? [control.validator, Validators.required]
      : Validators.required;
   control.setValidators(validators);
   control.updateValueAndValidity();
}

Notice that in this snippet we don't replace validators, instead, we add the required validation to any existing validations.

Here is an updated StackBlitz demo . Have a look at the personal.component.ts . It's a composite CVA, but the logic would be pretty much the same in your case.

I also wrote this article about custom form components. Although it does not involve a Material input, it may still be helpful to you.

Finally, you can check this guide on custom form controls, from the official Material website.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM