在 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() { } }