angular js创建表单_Angular 4.x 动态创建表单实例

本文将介绍如何动态创建表单组件,我们最终实现的效果如下:

在阅读本文之前,请确保你已经掌握 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: `

`

})

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: `

`

})

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: `

{{ config.label }}

type="text"

[attr.placeholder]="config.placeholder"

[formControlName]="config.name" />

`

})

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: `

{{ config.label }}

{{ config.placeholder }}

{{ option }}

`

})

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: `

{{ config.label }}

`

})

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 内置的 指令上。 是一个逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 comment元素。因此配合 指令,我们只会在 DOM 中看到我们自定义的组件,而不会看到元素 (因为 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(component);

}

// ...

}

现在我们引用了配置中定义的给定类型的组件,并将其传递给 ComponentFactoryRsolver 对象提供的resolveComponentFactory()方法。您可能已经注意到我们在 resolveComponentFactory 旁边使用了 ,这是因为我们要创建不同类型的组件。此外我们也可以定义一个接口,然后每个组件都去实现,如果这样的话 any就可以替换成我们已定义的接口。

现在我们已经有了组件工厂,我们可以简单地告诉我们的 ViewContainerRef 为我们创建这个组件:

@Directive(...)

export class DynamicFieldDirective implements OnInit {

// ...

component: any;

ngOnInit() {

const component = components[this.config.type];

const factory = this.resolver.resolveComponentFactory(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(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: `

class="dynamic-form"

[formGroup]="form">

*ngFor="let field of config;"

dynamicField

[config]="field"

[group]="form">

`

})

export class DynamicFormComponent implements OnInit {

// ...

}

正如我们前面提到的,我们使用作为容器来重复我们的动态字段。当我们的组件被渲染时,这是不可见的,这意味着我们只会在 DOM 中看到我们的动态创建的组件。

此外我们使用*ngFor结构指令,根据 config (数组配置项) 动态创建组件,并设置 dynamicField指令的两个输入属性:config 和 group。最后我们需要做的是实现表单提交功能。

表单提交

我们需要做的是为我们的

组件添加一个(ngSubmit)事件的处理程序,并在我们的动态表单组件中新增一个 @Output 输出属性,以便我们可以通知使用它的组件。

import { Component, Input, Output, OnInit, EventEmitter} from '@angular/core';

import { FormGroup, FormBuilder } from '@angular/forms';

@Component({

selector: 'dynamic-form',

template: `

[formGroup]="form"

(ngSubmit)="submitted.emit(form.value)">

*ngFor="let field of config;"

dynamicField

[config]="field"

[group]="form">

`

})

export class DynamicFormComponent implements OnInit {

@Input() config: any[] = [];

@Output() submitted: EventEmitter = new EventEmitter();

// ...

}

最后我们同步更新一下app.component.ts文件:

import { Component } from '@angular/core';

@Component({

selector: 'exe-app',

template: `

[config]="config"

(submitted)="formSubmitted($event)">

`

})

export class AppComponent {

// ...

formSubmitted(value: any) {

console.log(value);

}

}

我有话说

在自定义表单控件组件中[formGroup]="group" 是必须的么?

form-input.component.ts

{{ config.label }}

type="text"

[attr.placeholder]="config.placeholder"

[formControlName]="config.name" />

如果去掉

元素上的 [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:

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;

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值