Angular动态添加表单控件:父组件自定义搜索表单内容的实现问题
问题背景
想要在页面(比如HomePageComponent)里通过嵌套方式,给搜索表单动态加任意数量的筛选器组件,期望用法是:
<app-search-form> <app-searchbox></app-searchbox> </app-search-form>
但当前代码运行时会报错:NullInjectorError: NullInjectorError: No provider for ControlContainer!,原因是嵌套的筛选器组件(比如SearchboxComponent)会默认从直接父组件(HomePageComponent)找ControlContainer,但HomePage没有表单,没法提供,而不是去外层的SearchFormComponent拿表单容器。
解决方案
1. 修改SearchFormComponent,暴露表单容器
在SearchFormComponent的配置里添加ControlContainer的提供者,让内部嵌套的组件能正确定位到表单。修改后的ts代码:
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormGroupDirective, ControlContainer } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms'; @Component({ selector: 'app-search-form', standalone: true, imports: [ReactiveFormsModule], templateUrl: './search-form.component.html', styleUrl: './search-form.component.css', // 提供ControlContainer,指向当前组件的FormGroupDirective providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }] }) export class SearchFormComponent implements OnInit { form!: FormGroup; constructor( private formBuilder: FormBuilder, private formGroupDirective: FormGroupDirective ) { } ngOnInit() { this.form = this.formBuilder.group({}); // 将创建的表单绑定到FormGroupDirective this.formGroupDirective.form = this.form; } search() { console.log('Searching...'); console.log(this.form.value); } }
注意:这里要移除原来import里的SearchboxComponent,因为筛选器组件由使用页面自行导入。
2. 调整筛选器组件(以SearchboxComponent为例)
删掉原来的viewProviders配置,让组件自动向上查找SearchFormComponent提供的ControlContainer:
import { Component, OnInit } from '@angular/core'; import { FormControl, FormGroup, ControlContainer } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms'; @Component({ selector: 'app-searchbox', standalone: true, imports: [ReactiveFormsModule], templateUrl: './searchbox.component.html', styleUrl: './searchbox.component.css' // 移除原有的viewProviders配置 }) export class SearchboxComponent implements OnInit { formGroup!: FormGroup; constructor(private controlContainer: ControlContainer) { } ngOnInit() { this.formGroup = this.controlContainer.control as FormGroup; this.formGroup.addControl("searchbox", new FormControl('', null)); } }
3. 页面中正确使用
在HomePageComponent里,导入SearchFormComponent和需要的筛选器组件,直接嵌套使用即可:
import { Component } from '@angular/core'; import { SearchFormComponent } from './path/to/search-form.component'; import { SearchboxComponent } from './path/to/searchbox.component'; // 后续添加的筛选器组件,比如SearchCategoryFilterComponent也在这里导入 @Component({ selector: 'app-home-page', standalone: true, imports: [SearchFormComponent, SearchboxComponent], templateUrl: './home-page.component.html', styleUrl: './home-page.component.css' }) export class HomePageComponent {}
页面模板:
<app-search-form> <app-searchbox></app-searchbox> <!-- 可以自由添加其他筛选器组件,比如SearchCategoryFilterComponent、SearchLocationFilterComponent --> <!-- <app-search-category-filter></app-search-category-filter> --> </app-search-form>
原理说明
通过在SearchFormComponent中提供指向自身FormGroupDirective的ControlContainer,嵌套在<ng-content>里的子组件会向上查找到这个表单容器,而不是错误地去找页面组件的容器。这样不管加多少个筛选器组件,只要嵌套在<app-search-form>内部,都会自动注册到表单中,同时还能在页面里给每个筛选器添加自定义样式。
内容的提问来源于stack exchange,提问作者ScubaSteve

