Angular 4.x 动态创建表单实例
本文将介绍如何动态创建表单组件,我们最终实现的效果 如下:
在阅读本文之前,请确保你已经掌握 Angular 响应式表单和动态创建组件的相关知识,如果对相关知识还不了解,推荐先阅读一下 和 这两篇 文章 。对于已掌握的读者,我们直接进入主题 。
创建动态表单
创建 DynamicFormModule
在当前目录先创建 dynamic-form
目录,然后在该目录下创建 dynamic-form.module.ts
文件 ,文件 内容 如下:
dynamic-form/dynamic-form.module.ts
rush:js;">
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
CommonModule,ReactiveFormsModule
]
})
export class DynamicFormModule {}
创建完 DynamicFormModule
模块,接着我们需要在 AppModule 中导入该模块:
rush:js;">
import { NgModule } from '@angular/core';
import {
bro wserModule } from '@angular/platform-
bro wser';
import { DynamicFormModule } from './dynamic-form/dynamic-form.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [bro wserModule,DynamicFormModule],declara tions: [AppComponent],bootstrap: [AppComponent]
})
export class AppModule { }
创建 DynamicForm 容器
进入 dynamic-form
目录,在创建完 containers
目录后,继续创建 dynamic-form
目录,然后在该目录创建一个 名为 dynamic-form.component.ts
的文件 ,文件 内容 如下:
rush:js;">
import { Component,Input,OnInit } from '@angular/core';
import { FormGroup,FormBuilder } from '@angular/forms';
@Component({
selector: 'dynamic-form',template: `
<form [formGroup]="form">
`
})
export class DynamicFormComponent implements OnInit {
@
input()
con
fig : 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
组件:
rush:js;">
import { DynamicFormComponent } from './containers/dynamic-form/dynamic-form.component';
@NgModule({
imports: [
CommonModule,ReactiveFormsModule
],declara tions: [
DynamicFormComponent
],exports: [
DynamicFormComponent
]
})
export class DynamicFormModule {}
现在我们已经创建了表单,让我们实际使用它。
使用动态表单
打开 app.component.ts 文件 ,在组件模板中引入我们创建的 dynamic-form
组件,并设置相关的配置对象,具体示例如下:
app.component.ts
rush:js;">
import { Component } from '@angular/core';
interface FormItemOption {
type: string;
label: string;
name: string;
placeholder?: string;
options?: string[]
}
@Component({
selector: 'exe-app',template: `
`
})
export class AppComponent {
con
fig : 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
`
})
export class FormInputComponent {
con
fig : any;
group: FormGroup;
}
上面代码 中,我们在 FormInputComponent 组件类中定义了 config
和 group
两个属性 ,但我们并没有使用 @Input
装饰器来定义它们,因为我们不会以传统的方式来使用这个组件。接下来,我们来定义 select
和 button
组件。
FormSelectComponent
rush:js;">
import { Component } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'form-select',template: `
<div [formGroup]="group">
{{ config .label }}
fig.name">
fig.placeholder }}
fig.options">
{{ option }}
`
})
export class FormSelectComponent {
con
fig : Object;
group: FormGroup;
}
FormSelectComponent 组件与 FormInputComponent 组件的主要区别是,我们需要循环配置中定义的options属性 。这用于向用户 显示 所有的选项,我们还使用占位符属性 ,作为默 认的选项。
FormButtonComponent
rush:js;">
import { Component } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'form-button',template: `
<div [formGroup]="group">
<button type="submit">
{{ config .label }}
`
})
export class FormButtonComponent{
con
fig : Object;
group: FormGroup;
}
以上代码 ,我们只是定义了一个 简单的按钮,它使用 config .label
的值作为按钮文本。与所有组件一样,我们需要在前面创建的模块中声明这些自定义 组件。打开 dynamic-form.module.ts
文件 并添加 相应声明:
rush:js;">
// ...
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({
// ...
declara tions: [
DynamicFormComponent,FormButtonComponent,FormInputComponent,FormSelectComponent
],exports: [
DynamicFormComponent
]
})
export class DynamicFormModule {}
到目前为止,我们已经创建了三个组件。若想动态的创建这三个组件,我们将定义一个 指令,该指令的功能 跟 router-outlet
指令类似。接下来在 components
目录内部,我们新建一个 dynamic-field
目录,然后创建 dynamic-field.directive.ts
文件 。该文件 的内容 如下:
rush:js;">
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 内置的
指令上。
是一个 逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 comment
元素。因此配合
指令,我们只会在 DOM 中看到我们自定义 的组件,而不会看到
元素 (因为 DynamicFieldDirective 指令的 selector 被设置为 [dynamicField] )。
另外在指令中,我们使用 @Input
装饰器定义了两个输入属性 ,用于动态设置 config
和 group
对象。接下来我们开始动态渲染组件。
动态渲染组件,我们需要用到 ComponentFactoryResolver
和 ViewContainerRef
两个对象。ComponentFactoryResolver
对象用于创建对应类型的组件工厂 (ComponentFactory),而 ViewContainerRef
对象用于表示一个 视图容器,可添加 一个 或多个视图,通过它我们可以方便地创建和管理内嵌视图或组件视图。
让我们在 DynamicFieldDirective 指令构造函数 中,注入相关对象,具体代码 如下:
rush:js;">
import { ComponentFactoryResolver,Directive,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
类型来声明组件的类型,因此我们需要创建一个 对象来将字符串映射到相关的组件类,具体如下:
rush:js;">
// ...
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 (组件工厂):
rush:js;">
// ...
const components = {
button: FormButtonComponent,select: FormSelectComponent
};
@Directive(...)
export class DynamicFieldDirective implements OnInit {
// ...
ngOnInit() {
const component = components[this.config .type];
const factory = this.resolver.resolveComponentFactory(component);
}
// ...
}
现在我们引用了配置中定义的给定类型的组件,并将其传递给 ComponentFactoryRsolver 对象提供的resolveComponentFactory()
方法。您可能已经注意到我们在 resolveComponentFactory 旁边使用了 ,
这是因为我们要创建不同类型的组件。此外我们也可以定义一个接口,然后每个组件都去实现,如果这样的话 any
就可以替换成我们已定义的接口。
现在我们已经有了组件工厂,我们可以简单地告诉我们的 ViewContainerRef 为我们创建这个组件:
ngOnInit() {
const component = components[this.config.type];
const factory = this.resolver.resolveComponentFactory
(component);
this.component = this.container.createComponent(factory);
}
// ...
}
我们现在已经可以将 config
和 group
传递到我们动态创建的组件中。我们可以通过 this.component.instance
访问到组件类的实例:
ngOnInit() {
const component = components[this.config.type];
const factory = this.resolver.resolveComponentFactory
(component);
this.component = this.container.createComponent(factory);
this.component.instance.config = this.config;
this.component.instance.group = this.group;
}
// ...
}
接下来,让我们在 DynamicFormModule
中声明已创建的 DynamicFieldDirective
指令:
@NgModule({
// ...
declarations: [
DynamicFieldDirective,DynamicFormComponent,exports: [
DynamicFormComponent
]
})
export class DynamicFormModule {}
如果我们直接在浏览器中运行以上程序,控制台会抛出异常。当我们想要通过 ComponentFactoryResolver
对象动态创建组件的话,我们需要在 @NgModule
配置对象的一个属性 - entryComponents 中,声明需动态加载的组件。
基本工作都已经完成,现在我们需要做的就是更新 DynamicFormComponent
组件,应用我们之前已经 DynamicFieldDirective
实现动态组件的创建:
`
})
export class DynamicFormComponent implements OnInit {
// ...
}
正如我们前面提到的,我们使用
作为容器来重复我们的动态字段。当我们的组件被渲染时,这是不可见的,这意味着我们只会在 DOM 中看到我们的动态创建的组件。
此外我们使用 *ngFor
结构指令,根据 config (数组配置项) 动态创建组件,并设置 dynamicField
指令的两个输入属性:config 和 group。最后我们需要做的是实现表单提交功能。
表单提交
我们需要做的是为我们的
组件添加一个 (ngSubmit)
事件的处理程序,并在我们的动态表单组件中新增一个 @Output
输出属性,以便我们可以通知使用它的组件。
`
})
export class DynamicFormComponent implements OnInit {
@Input() config: any[] = [];
@Output() submitted: EventEmitter = new EventEmitter();
// ...
}
最后我们同步更新一下 app.component.ts
文件:
@Component({
selector: 'exe-app',template: `
<div class="app">
<dynamic-form
[config]="config"
(submitted)="formSubmitted($event)">
`
})
export class AppComponent {
// ...
formSubmitted(value: any) {
console.log(value);
}
}
rush:js;">
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();
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。