Angular 4.x 动态创建表单实例
发布时间 - 2026-01-11 00:49:45 点击率:次本文将介绍如何动态创建表单组件,我们最终实现的效果如下:
在阅读本文之前,请确保你已经掌握 Angular 响应式表单和动态创建组件的相关知识,如果对相关知识还不了解,推荐先阅读一下 Angular 4.x Reactive Forms 和 Angular 4.x 动态创建组件 这两篇文章。对于已掌握的读者,我们直接进入主题。
创建动态表单
创建 DynamicFormModule
在当前目录先创建 dynamic-form 目录,然后在该目录下创建 dynamic-form.module.ts 文件,文件内容如下:
dynamic-form/dynamic-form.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule
]
})
export class DynamicFormModule {}
创建完 DynamicFormModule 模块,接着我们需要在 AppModule 中导入该模块:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { DynamicFormModule } from './dynamic-form/dynamic-form.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule, DynamicFormModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
创建 DynamicForm 容器
进入 dynamic-form 目录,在创建完 containers 目录后,继续创建 dynamic-form 目录,然后在该目录创建一个名为 dynamic-form.component.ts 的文件,文件内容如下:
import { Component, Input, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
@Component({
selector: 'dynamic-form',
template: `
<form [formGroup]="form">
</form>
`
})
export class DynamicFormComponent implements OnInit {
@Input()
config: any[] = [];
form: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.form = this.createGroup();
}
createGroup() {
const group = this.fb.group({});
this.config.forEach(control => group.addControl(control.name, this.fb.control('')));
return group;
}
}
由于我们的表单是动态的,我们需要接受一个数组类型的配置对象才能知道需要动态创建的内容。因此,我们定义了一个 config 输入属性,用于接收数组类型的配置对象。
此外我们利用了 Angular 响应式表单,提供的 API 动态的创建 FormGroup 对象。对于配置对象中的每一项,我们要求该项至少包含两个属性,即 (type) 类型和 (name) 名称:
- type - 用于设置表单项的类型,如
input、select、button等 - name - 用于设置表单控件的 name 属性
在 createGroup() 方法中,我们循环遍历输入的 config 属性,然后利用 FormGroup 对象提供的 addControl() 方法,动态地添加新建的表单控件。
接下来我们在 DynamicFormModule 模块中声明并导出新建的 DynamicFormComponent 组件:
import { DynamicFormComponent } from './containers/dynamic-form/dynamic-form.component';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule
],
declarations: [
DynamicFormComponent
],
exports: [
DynamicFormComponent
]
})
export class DynamicFormModule {}
现在我们已经创建了表单,让我们实际使用它。
使用动态表单
打开 app.component.ts 文件,在组件模板中引入我们创建的 dynamic-form 组件,并设置相关的配置对象,具体示例如下:
app.component.ts
import { Component } from '@angular/core';
interface FormItemOption {
type: string;
label: string;
name: string;
placeholder?: string;
options?: string[]
}
@Component({
selector: 'exe-app',
template: `
<div>
<dynamic-form [config]="config"></dynamic-form>
</div>
`
})
export class AppComponent {
config: FormItemOption[] = [
{
type: 'input',
label: 'Full name',
name: 'name',
placeholder: 'Enter your name'
},
{
type: 'select',
label: 'Favourite food',
name: 'food',
options: ['Pizza', 'Hot Dogs', 'Knakworstje', 'Coffee'],
placeholder: 'Select an option'
},
{
type: 'button',
label: 'Submit',
name: 'submit'
}
];
}
上面代码中,我们在 AppComponent 组件类中设置了 config 配置对象,该配置对象中设置了三种类型的表单类型。对于每个表单项的配置对象,我们定义了一个 FormItemOption 数据接口,该接口中我们定义了三个必选属性:type、label 和 name 及两个可选属性:options 和 placeholder。下面让我们创建对应类型的组件。
自定义表单项组件
FormInputComponent
在 dynamic-form 目录,我们新建一个 components 目录,然后创建 form-input、form-select 和 form-button 三个文件夹。创建完文件夹后,我们先来定义 form-input 组件:
form-input.component.ts
import { Component, ViewContainerRef } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'form-input',
template: `
<div [formGroup]="group">
<label>{{ config.label }}</label>
<input
type="text"
[attr.placeholder]="config.placeholder"
[formControlName]="config.name" />
</div>
`
})
export class FormInputComponent {
config: any;
group: FormGroup;
}
上面代码中,我们在 FormInputComponent 组件类中定义了 config 和 group 两个属性,但我们并没有使用 @Input 装饰器来定义它们,因为我们不会以传统的方式来使用这个组件。接下来,我们来定义 select 和 button 组件。
FormSelectComponent
import { Component } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'form-select',
template: `
<div [formGroup]="group">
<label>{{ config.label }}</label>
<select [formControlName]="config.name">
<option value="">{{ config.placeholder }}</option>
<option *ngFor="let option of config.options">
{{ option }}
</option>
</select>
</div>
`
})
export class FormSelectComponent {
config: Object;
group: FormGroup;
}
FormSelectComponent 组件与 FormInputComponent 组件的主要区别是,我们需要循环配置中定义的options属性。这用于向用户显示所有的选项,我们还使用占位符属性,作为默认的选项。
FormButtonComponent
import { Component } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'form-button',
template: `
<div [formGroup]="group">
<button type="submit">
{{ config.label }}
</button>
</div>
`
})
export class FormButtonComponent{
config: Object;
group: FormGroup;
}
以上代码,我们只是定义了一个简单的按钮,它使用 config.label 的值作为按钮文本。与所有组件一样,我们需要在前面创建的模块中声明这些自定义组件。打开 dynamic-form.module.ts 文件并添加相应声明:
// ...
import { FormButtonComponent } from './components/form-button/form-button.component';
import { FormInputComponent } from './components/form-input/form-input.component';
import { FormSelectComponent } from './components/form-select/form-select.component';
@NgModule({
// ...
declarations: [
DynamicFormComponent,
FormButtonComponent,
FormInputComponent,
FormSelectComponent
],
exports: [
DynamicFormComponent
]
})
export class DynamicFormModule {}
到目前为止,我们已经创建了三个组件。若想动态的创建这三个组件,我们将定义一个指令,该指令的功能跟 router-outlet 指令类似。接下来在 components 目录内部,我们新建一个 dynamic-field 目录,然后创建 dynamic-field.directive.ts 文件。该文件的内容如下:
import { Directive, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Directive({
selector: '[dynamicField]'
})
export class DynamicFieldDirective {
@Input()
config: Object;
@Input()
group: FormGroup;
}
我们将指令的 selector 属性设置为 [dynamicField],因为我们将其应用为属性而不是元素。
这样做的好处是,我们的指令可以应用在 Angular 内置的 <ng-container> 指令上。 <ng-container> 是一个逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 comment 元素。因此配合 <ng-container> 指令,我们只会在 DOM 中看到我们自定义的组件,而不会看到 <dynamic-field> 元素 (因为 DynamicFieldDirective 指令的 selector 被设置为 [dynamicField] )。
另外在指令中,我们使用 @Input 装饰器定义了两个输入属性,用于动态设置 config 和 group 对象。接下来我们开始动态渲染组件。
动态渲染组件,我们需要用到 ComponentFactoryResolver 和 ViewContainerRef 两个对象。ComponentFactoryResolver 对象用于创建对应类型的组件工厂 (ComponentFactory),而 ViewContainerRef 对象用于表示一个视图容器,可添加一个或多个视图,通过它我们可以方便地创建和管理内嵌视图或组件视图。
让我们在 DynamicFieldDirective 指令构造函数中,注入相关对象,具体代码如下:
import { ComponentFactoryResolver, Directive, Input, OnInit,
ViewContainerRef } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Directive({
selector: '[dynamicField]'
})
export class DynamicFieldDirective implements OnInit {
@Input()
config;
@Input()
group: FormGroup;
constructor(
private resolver: ComponentFactoryResolver,
private container: ViewContainerRef
) {}
ngOnInit() {
}
}
上面代码中,我们还添加了 ngOnInit 生命周期钩子。由于我们允许使用 input 或 select 类型来声明组件的类型,因此我们需要创建一个对象来将字符串映射到相关的组件类,具体如下:
// ...
import { FormButtonComponent } from '../form-button/form-button.component';
import { FormInputComponent } from '../form-input/form-input.component';
import { FormSelectComponent } from '../form-select/form-select.component';
const components = {
button: FormButtonComponent,
input: FormInputComponent,
select: FormSelectComponent
};
@Directive(...)
export class DynamicFieldDirective implements OnInit {
// ...
}
这将允许我们通过 components['button'] 获取对应的 FormButtonComponent 组件类,然后我们可以把它传递给 ComponentFactoryResolver 对象以获取对应的 ComponentFactory (组件工厂):
// ...
const components = {
button: FormButtonComponent,
input: FormInputComponent,
select: FormSelectComponent
};
@Directive(...)
export class DynamicFieldDirective implements OnInit {
// ...
ngOnInit() {
const component = components[this.config.type];
const factory = this.resolver.resolveComponentFactory<any>(component);
}
// ...
}
现在我们引用了配置中定义的给定类型的组件,并将其传递给 ComponentFactoryRsolver 对象提供的resolveComponentFactory() 方法。您可能已经注意到我们在 resolveComponentFactory 旁边使用了 <any>,这是因为我们要创建不同类型的组件。此外我们也可以定义一个接口,然后每个组件都去实现,如果这样的话 any 就可以替换成我们已定义的接口。
现在我们已经有了组件工厂,我们可以简单地告诉我们的 ViewContainerRef 为我们创建这个组件:
@Directive(...)
export class DynamicFieldDirective implements OnInit {
// ...
component: any;
ngOnInit() {
const component = components[this.config.type];
const factory = this.resolver.resolveComponentFactory<any>(component);
this.component = this.container.createComponent(factory);
}
// ...
}
我们现在已经可以将 config 和 group 传递到我们动态创建的组件中。我们可以通过 this.component.instance 访问到组件类的实例:
@Directive(...)
export class DynamicFieldDirective implements OnInit {
// ...
component;
ngOnInit() {
const component = components[this.config.type];
const factory = this.resolver.resolveComponentFactory<any>(component);
this.component = this.container.createComponent(factory);
this.component.instance.config = this.config;
this.component.instance.group = this.group;
}
// ...
}
接下来,让我们在 DynamicFormModule 中声明已创建的 DynamicFieldDirective 指令:
// ...
import { DynamicFieldDirective } from './components/dynamic-field/dynamic-field.directive';
@NgModule({
// ...
declarations: [
DynamicFieldDirective,
DynamicFormComponent,
FormButtonComponent,
FormInputComponent,
FormSelectComponent
],
exports: [
DynamicFormComponent
]
})
export class DynamicFormModule {}
如果我们直接在浏览器中运行以上程序,控制台会抛出异常。当我们想要通过 ComponentFactoryResolver 对象动态创建组件的话,我们需要在 @NgModule 配置对象的一个属性 - entryComponents 中,声明需动态加载的组件。
@NgModule({
// ...
entryComponents: [
FormButtonComponent,
FormInputComponent,
FormSelectComponent
]
})
export class DynamicFormModule {}
基本工作都已经完成,现在我们需要做的就是更新 DynamicFormComponent 组件,应用我们之前已经 DynamicFieldDirective 实现动态组件的创建:
@Component({
selector: 'dynamic-form',
template: `
<form
class="dynamic-form"
[formGroup]="form">
<ng-container
*ngFor="let field of config;"
dynamicField
[config]="field"
[group]="form">
</ng-container>
</form>
`
})
export class DynamicFormComponent implements OnInit {
// ...
}
正如我们前面提到的,我们使用 <ng-container>作为容器来重复我们的动态字段。当我们的组件被渲染时,这是不可见的,这意味着我们只会在 DOM 中看到我们的动态创建的组件。
此外我们使用 *ngFor 结构指令,根据 config (数组配置项) 动态创建组件,并设置 dynamicField 指令的两个输入属性:config 和 group。最后我们需要做的是实现表单提交功能。
表单提交
我们需要做的是为我们的 <form> 组件添加一个 (ngSubmit) 事件的处理程序,并在我们的动态表单组件中新增一个 @Output 输出属性,以便我们可以通知使用它的组件。
import { Component, Input, Output, OnInit, EventEmitter} from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
@Component({
selector: 'dynamic-form',
template: `
<form
[formGroup]="form"
(ngSubmit)="submitted.emit(form.value)">
<ng-container
*ngFor="let field of config;"
dynamicField
[config]="field"
[group]="form">
</ng-container>
</form>
`
})
export class DynamicFormComponent implements OnInit {
@Input() config: any[] = [];
@Output() submitted: EventEmitter<any> = new EventEmitter<any>();
// ...
}
最后我们同步更新一下 app.component.ts 文件:
import { Component } from '@angular/core';
@Component({
selector: 'exe-app',
template: `
<div class="app">
<dynamic-form
[config]="config"
(submitted)="formSubmitted($event)">
</dynamic-form>
</div>
`
})
export class AppComponent {
// ...
formSubmitted(value: any) {
console.log(value);
}
}
Toddmotto 大神线上完整代码请访问- toddmott/angular-dynamic-forms。
我有话说
在自定义表单控件组件中 [formGroup]="group" 是必须的么?
form-input.component.ts
<div [formGroup]="group">
<label>{{ config.label }}</label>
<input
type="text"
[attr.placeholder]="config.placeholder"
[formControlName]="config.name" />
</div>
如果去掉 <div> 元素上的 [formGroup]="group" 属性,重新编译后浏览器控制台将会抛出以下异常:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
Example:
<div [formGroup]="myGroup">
<input formControlName="firstName">
</div>
In your class:
this.myGroup = new FormGroup({
firstName: new FormControl()
});
在 formControlName 指令中,初始化控件的时候,会验证父级指令的类型:
private _checkParentType(): void {
if (!(this._parent instanceof FormGroupName) &&
this._parent instanceof AbstractFormGroupDirective) {
ReactiveErrors.ngModelGroupException();
} else if (
!(this._parent instanceof FormGroupName) &&
!(this._parent instanceof FormGroupDirective) &&
!(this._parent instanceof FormArrayName)) {
ReactiveErrors.controlParentException();
}
}
那为什么要验证,是因为要把新增的控件添加到对应 formDirective 对象中:
private _setUpControl() {
this._checkParentType();
this._control = this.formDirective.addControl(this);
if (this.control.disabled && this.valueAccessor !.setDisabledState) {
this.valueAccessor !.setDisabledState !(true);
}
this._added = true;
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# angular4
# 动态表单
# angularjs
# angular
# angular4中关于表单的校验示例
# Angular4表单验证代码详解
# Angular4实现动态添加删除表单输入框功能
# AngularJS实现表单验证
# AngularJS表单编辑提交功能实例
# angular实现表单验证及提交功能
# AngularJS的表单使用详解
# 详解Angular 4 表单快速入门
# Angular 4.x中表单Reactive Forms详解
# 详细解读AngularJS中的表单验证编程
# Angular4编程之表单响应功能示例
# 表单
# 我们可以
# 让我们
# 自定义
# 的是
# 象中
# 会在
# 当我们
# 设置为
# 创建一个
# 抛出
# 相关知识
# 新建一个
# 类中
# 是一个
# 这是
# 是因为
# 多个
# 将会
# 我有
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
专业商城网站制作公司有哪些,pi商城官网是哪个?
Laravel请求验证怎么写_Laravel Validator自定义表单验证规则教程
Laravel怎么自定义错误页面_Laravel修改404和500页面模板
jquery插件bootstrapValidator表单验证详解
Laravel如何处理文件上传_Laravel Storage门面实现文件存储与管理
儿童网站界面设计图片,中国少年儿童教育网站-怎么去注册?
Laravel storage目录权限问题_Laravel文件写入权限设置
Edge浏览器提示“由你的组织管理”怎么解决_去除浏览器托管提示【修复】
高端云建站费用究竟需要多少预算?
HTML透明颜色代码在Angular里怎么设置_Angular透明颜色使用指南【详解】
Laravel如何使用Telescope进行调试?(安装和使用教程)
HTML5空格和nbsp有啥关系_nbsp的作用及使用场景【说明】
如何在云虚拟主机上快速搭建个人网站?
深圳网站制作的公司有哪些,dido官方网站?
创业网站制作流程,创业网站可靠吗?
如何快速建站并高效导出源代码?
如何在香港服务器上快速搭建免备案网站?
Laravel怎么使用Blade模板引擎_Laravel模板继承与Component组件复用【手册】
微信小程序 canvas开发实例及注意事项
北京企业网站设计制作公司,北京铁路集团官方网站?
Laravel如何实现用户角色和权限系统_Laravel角色权限管理机制
高端网站建设与定制开发一站式解决方案 中企动力
如何在橙子建站上传落地页?操作指南详解
Java Adapter 适配器模式(类适配器,对象适配器)优缺点对比
如何快速生成橙子建站落地页链接?
C++时间戳转换成日期时间的步骤和示例代码
详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)
iOS正则表达式验证手机号、邮箱、身份证号等
Laravel中的Facade(门面)到底是什么原理
Laravel如何将应用部署到生产服务器_Laravel生产环境部署流程
详解CentOS6.5 安装 MySQL5.1.71的方法
企业网站制作这些问题要关注
音乐网站服务器如何优化API响应速度?
Laravel如何使用Gate和Policy进行权限控制_Laravel权限判定与策略规则配置
Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】
Laravel数据库迁移怎么用_Laravel Migration管理数据库结构的正确姿势
公司网站制作价格怎么算,公司办个官网需要多少钱?
Laravel如何处理和验证JSON类型的数据库字段
C语言设计一个闪闪的圣诞树
Laravel如何正确地在控制器和模型之间分配逻辑_Laravel代码职责分离与架构建议
Laravel如何升级到最新版本?(升级指南和步骤)
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
Laravel如何设置自定义的日志文件名_Laravel根据日期或用户ID生成动态日志【技巧】
java获取注册ip实例
laravel怎么使用数据库工厂(Factory)生成带有关联模型的数据_laravel Factory生成关联数据方法
Laravel怎么实现观察者模式Observer_Laravel模型事件监听与解耦开发【指南】
Laravel如何使用Blade模板引擎?(完整语法和示例)
大连网站制作费用,大连新青年网站,五年四班里的视频怎样下载啊?
html5的keygen标签为什么废弃_替代方案说明【解答】
如何用AI一键生成爆款短视频文案?小红书AI文案写作指令【教程】

