Angular 14自定义mat-select无法展开显示选项排查求助
问题描述
基于Angular 14开发了一个可复用的mat-select组件库,目前遇到mat-select无法展开显示选项的问题,控制台没有报错。想请教这是否与NG_VALUE_ACCESSOR有关?或是Angular Material在Angular 14版本中有相关变更?
相关代码
lib-mat-select.html
<mat-form-field appearance="legacy"> <!-- label --> <mat-label>Label</mat-label> <!-- icon --> <mat-icon *ngIf="controler.value && !controler.disabled" (click)="clearInput(); $event.stopPropagation()"> highlight_off </mat-icon> <!-- mat-select --> <mat-select [formControl]="controler" disableOptionCentering="true" panelClass="panelClass" (selectionChange)="onSelectionChange($event)"> <!-- mat-options --> <mat-option *ngFor="let item of options | keyvalue" [value]="item.key"> {{item.value}} </mat-option> </mat-select> </mat-form-field>
lib-mat-select.ts
@Component({ selector: "lib-mat-select", templateUrl: "./lib-mat-select.component.html", styleUrls: ["./lib-mat-select.component.scss"], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => LibMatSelectComponent), multi: true, }, ], }) export class LibMatSelectComponent implements ControlValueAccessor, OnInit { @Input() public formControlName: string; @Input() public label: string; @Input() public placeholder: string; @Input() public options: any; @Output() public selectionChangeEmitter = new EventEmitter(); public controler: FormControl; constructor(private controlContainer: ControlContainer) {} ngOnInit(): void { this.initController(); } public onSelectionChange(event) { this.selectionChangeEmitter.emit(event); } private initController() { if (this.controlContainer && this.formControlName) { this.controler = this.controlContainer.control?.get( this.formControlName ) as FormControl; } } public clearInput() { this.controler.setValue(null); } // Les méthodes de l'interface ControlValueAccessor qui ne nécessite pas spécialement d'être surchagée writeValue(obj: any): void {} registerOnChange(fn: any): void {} registerOnTouched(fn: any): void {} }
app.component.html
<form [formGroup]="form"> <div class="center-screen"> <lib-mat-select formControlName="fruits" [options]="fruitList"></lib-mat-select> </div> </form>
app.component.ts
fruitList: { 1: 'Pomme', 2: 'Orange', 3: 'Framboise'} public form: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit(): void { this.createForm(); } public createForm() { this.form = this.fb.group({ fruits: null, }); }
解决方案
问题核心在于组件同时实现ControlValueAccessor又通过controlContainer绑定外部表单控件,两种表单状态管理方式冲突,导致mat-select交互逻辑异常,和Angular Material 14版本变更无关。
具体问题点
- 注册
NG_VALUE_ACCESSOR意味着组件要作为自定义表单控件独立管理状态,但你却直接将外部FormControl绑定到内部mat-select,导致控件状态脱节,下拉面板触发逻辑失效。 - 空实现的
ControlValueAccessor方法没有起到任何作用,反而干扰了Angular表单的状态同步机制。
修复方案(二选一)
方案1:移除ControlValueAccessor,保留外部表单绑定
适合组件依赖父表单的场景:
- 删除组件
providers中的NG_VALUE_ACCESSOR配置,去掉implements ControlValueAccessor及对应空方法。 - 修复模板中硬编码的label,绑定到组件输入属性:
<mat-label>{{label}}</mat-label> - 为
controler添加兜底逻辑,避免空值报错:private initController() { if (this.controlContainer && this.formControlName) { this.controler = this.controlContainer.control?.get(this.formControlName) as FormControl; } else { this.controler = new FormControl(); } }
修改后的lib-mat-select.ts核心代码:
@Component({ selector: "lib-mat-select", templateUrl: "./lib-mat-select.component.html", styleUrls: ["./lib-mat-select.component.scss"], }) export class LibMatSelectComponent implements OnInit { @Input() public formControlName: string; @Input() public label: string; @Input() public placeholder: string; @Input() public options: any; @Output() public selectionChangeEmitter = new EventEmitter(); public controler: FormControl; constructor(private controlContainer: ControlContainer) {} ngOnInit(): void { this.initController(); } public onSelectionChange(event) { this.selectionChangeEmitter.emit(event); } private initController() { if (this.controlContainer && this.formControlName) { this.controler = this.controlContainer.control?.get( this.formControlName ) as FormControl; } else { this.controler = new FormControl(); } } public clearInput() { this.controler.setValue(null); } }
方案2:正确实现ControlValueAccessor,作为独立自定义控件
适合组件需要脱离父表单单独使用的场景:
- 保留
NG_VALUE_ACCESSOR配置,内部维护FormControl,正确实现所有ControlValueAccessor方法。 - 监听内部控件变化,同步状态到外部。
修改后的lib-mat-select.ts核心代码:
@Component({ selector: "lib-mat-select", templateUrl: "./lib-mat-select.component.html", styleUrls: ["./lib-mat-select.component.scss"], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => LibMatSelectComponent), multi: true, }, ], }) export class LibMatSelectComponent implements ControlValueAccessor { @Input() public label: string; @Input() public placeholder: string; @Input() public options: any; @Output() public selectionChangeEmitter = new EventEmitter(); public controler = new FormControl(); private onChange: (value: any) => void = () => {}; private onTouched: () => void = () => {}; ngOnInit(): void { this.controler.valueChanges.subscribe(value => { this.onChange(value); this.selectionChangeEmitter.emit(value); }); } public clearInput() { this.controler.setValue(null); this.onTouched(); } writeValue(obj: any): void { this.controler.setValue(obj); } registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; } setDisabledState(isDisabled: boolean): void { isDisabled ? this.controler.disable() : this.controler.enable(); } }
此时组件可直接绑定formControl或ngModel:
<lib-mat-select [formControl]="fruitsControl" [options]="fruitList"></lib-mat-select>
内容的提问来源于stack exchange,提问作者foo42
相关产品推荐
相关产品推荐

