Angular封装可复用mat-form-field组件报值访问器缺失错误
问题根因
报错No value accessor for form control with unspecified name attribute是因为Angular解析模板时,会在绑定了[formControl]的**宿主元素(即<app-form-field>标签本身)**上查找对应的ControlValueAccessor实现,而你把formControl绑定写在了组件内部的input上,外层自定义组件标签没有对应的值访问器,初始化表单指令时就会抛出错误。
另外代码中定义了appearance输入属性,但未绑定到<mat-form-field>上,配置的外观样式不会生效。
修复方法
方案一:实现ControlValueAccessor接口(官方推荐,可维护性强)
让自定义组件本身成为合法的表单控件,符合Angular Material自定义表单控件规范,后续扩展方便。
- 修改组件类代码,注入表单控件提供者,实现ControlValueAccessor接口:
import { Component, Input, forwardRef } from '@angular/core'; import { FormControl, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; @Component({ selector: 'app-form-field', templateUrl: './form-field.component.html', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FormFieldComponent), multi: true } ] }) export class FormFieldComponent implements ControlValueAccessor { @Input() inputType: "color" | "date" | "datetime-local" | "email" | "month" | "number" | "password" | "search" | "tel" | "text" | "time" | "url" | "week" = "text" @Input() appearance: 'legacy' | 'fill' | 'standard' | 'outline' = 'standard'; @Input() label: string; @Input() minLength: number; @Input() maxLength: number; @Input() formControl: FormControl; value: string; disabled = false; onChange = (value: string) => {}; onTouched = () => {}; writeValue(value: string): void { this.value = value ?? ''; } registerOnChange(fn: (value: string) => void): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } handleInput(event: Event) { const inputValue = (event.target as HTMLInputElement).value; this.value = inputValue; this.onChange(inputValue); } }
- 修改组件模板,移除input上的
[formControl]绑定,替换为值和事件绑定,补上appearance属性绑定:
<mat-form-field [appearance]="appearance" fxLayout="column" fxLayoutAlign="start stretch"> <mat-label>{{label}}</mat-label> <input matInput [type]="inputType" autocomplete="username" [value]="value" [disabled]="disabled" (input)="handleInput($event)" (blur)="onTouched()" > <mat-error *ngIf="formControl.touched && formControl.errors?.required"> Bitte geben Sie den {{label}} an! </mat-error> <mat-error *ngIf="formControl.touched && formControl.errors?.maxlength"> Der {{label}} darf nicht mehr als {{maxLength}} Zeichen haben! </mat-error> <mat-error *ngIf="formControl.touched && formControl.errors?.minlength"> Der {{label}} darf nicht weniger als {{minLength}} Zeichen haben! </mat-error> </mat-form-field> <ng-container *ngIf="formControl.touched && formControl.invalid"> <br/> </ng-container>
- 父组件原有调用代码无需修改,即可正常运行。
方案二:添加ngDefaultControl快速修复(适合临时场景)
如果不想修改组件类逻辑,直接在父组件调用自定义组件的标签上添加ngDefaultControl指令,为宿主元素注入默认值访问器即可消除报错:
<app-form-field ngDefaultControl [formControl]="form.controls.userName" [maxLength]="userNameMaxLength" [minLength]="userNameMinLength" [label]="'Benutzername'" ></app-form-field> <app-form-field ngDefaultControl [formControl]="form.controls.password" [maxLength]="passwordMaxLength" [minLength]="passwordMinLength" [label]="'Passwort'" [inputType]="'password'" ></app-form-field>
该方案属于快速兼容写法,自定义组件未实现标准表单控件接口,后续做禁用状态联动、自定义校验等功能时会有局限,不推荐在生产环境长期使用。
可选优化项
- 目前
autocomplete="username"是硬编码,密码框会识别错误,可新增输入属性自定义autocomplete值 - 给内部input绑定
[maxlength]、[minlength]属性,配合校验器限制输入长度 - 错误提示文案可改为可配置输入项,适配不同业务字段的提示需求
内容的提问来源于stack exchange,提问作者Crztean
相关产品推荐
相关产品推荐

