- 构建一个动态表单,动态生成控件,验证规则。
- 建立一个input组件,一个select组件
- 将组件注入到页面中并显示
前半部分直接借鉴官方文档的写法,后期开启个性化定制后,功能强大!
在 theme/components
目录下建立一个文件夹 dynamic-form
html
项目目录结构以下,下面跟着步骤一步一步来构建typescript
咱们须要定义一个对象模型,用来描述表单功能须要的场景。相关功能很是之多(相似于 input
select
)。数据库
先建立一个最基础的基类,名为 QuestionBase
,文件为 dynamic-form-base/question-base.ts
数组
export class QuestionBase<T>{ value: T; // 值,类型可选 key: string; // 字段名 label: string; // 控件前的文字提示 required: boolean; // 是否为必填 disabled: boolean; // 是否禁用 reg: string; // 正则 prompt: string; // 验证不经过提示 order: number; // 排序 controlType: string; // 渲染类型 constructor(options: { value?: T, key?: string, label?: string, required?: boolean, disabled?: boolean, reg?: string, prompt?: string, order?: number, controlType?: string } = {}) { // 设置各个值的默认值 this.value = options.value; this.key = options.key || ''; this.label = options.label || ''; this.required = !!options.required; this.disabled = !!options.disabled; this.reg = options.reg || ''; this.prompt = options.prompt || ''; this.order = options.order || 0; this.controlType = options.controlType || ''; } }
在这个基础上,咱们派生出两个类 InputQuestion
和 SelectQuestion
,表明文本框和下拉框。浏览器
这么作的缘由是根据不一样的控件,进行个性化定制,以及合理规范,还有动态渲染出合适的组件ide
InputQuestion
能够经过type属性来支持多种HTML元素类型(例如: text number email
) --- dynamic-form-base/question-input.ts
优化
import { QuestionBase } from './question-base'; export class InputQuestion extends QuestionBase<string | number> { controlType = 'input'; type: string; constructor(options: {} = {}) { super(options); this.type = options['type'] || ''; } }
SelectQuestion
表示一个带可选列表的选择框ui
import { QuestionBase } from './question-base'; export class SelectQuestion extends QuestionBase<string | number> { controlType = 'select'; options: any[] = []; constructor(options: {} = {}) { super(options); this.options = options['options'] || []; } }
最后别忘了用 index.ts
将这三个模型导出this
接下来,咱们定义一个 QuestionControlService
,一个能够把模型转换为FormGroup的服务。 简而言之,这个FormGroup使用问卷模型的元数据,并容许咱们设置默认值和验证规则spa
question-control.service.ts
import { Injectable } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { QuestionBase } from './dynamic-form-base'; @Injectable() export class QuestionControlService { constructor() { } // 转化为控件 toFormGroup(questions: QuestionBase<any>[]) { let group: any = {}; questions.forEach(question => { if (question.required) { // 选项为必填时 if (question.reg) { // 有正则的状况 group[question.key] = new FormControl(question.value || '', Validators.compose([Validators.required, Validators.pattern(question.reg)])); } else { group[question.key] = new FormControl(question.value || '', Validators.compose([Validators.required])); } } else if (!question.required && question.reg) { // 选项为非必填可是须要正则匹配的状况 group[question.key] = new FormControl(question.value || '', Validators.compose([Validators.pattern(question.reg)])); } else { group[question.key] = new FormControl(question.value || ''); } }); return new FormGroup(group); } }
如今咱们已经有了一个定义好的完整模型了,接着就能够开始构建一个展示动态表单的组件。
DynamicFormComponent
是表单的主要容器和入口
dynamic-form.component.html
<div> <form (ngSubmit)="onSubmit()" [formGroup]="form"> <div *ngFor="let question of questions" class="form-row"> <df-question [question]="question" [form]="form"></df-question> </div> <div class="form-row"> <button type="submit" [disabled]="!form.valid">保存</button> </div> </form> <div *ngIf="payload" class="form-row"> <strong>Saved the following values</strong><br>{{payload}} </div> </div>
dynamic-form.component.ts
import { Component, Input, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { QuestionBase } from './dynamic-form-base/question-base'; import { QuestionControlService } from './question-control.service'; @Component({ selector: 'dynamic-form', templateUrl: './dynamic-form.component.html', providers: [QuestionControlService] }) export class DynamicFormComponent implements OnInit { @Input() questions: QuestionBase<any>[] = []; form: FormGroup; payload = ''; constructor( private qcs: QuestionControlService ) { } ngOnInit() { console.log(this.questions); this.form = this.qcs.toFormGroup(this.questions); console.log(this.form); } onSubmit() { this.payload = JSON.stringify(this.form.value); } }
刚才建立的是一个组件入口,每一个 question
都被绑定到了 <df-question>
组件元素,<df-question>
标签匹配到的是组件 DynamicFormQuestionComponent
,该组件的职责是根据各个问卷问题对象的值来动态渲染表单控件。
如今来建立它的子组件 DynamicFormQuestionComponent
dynamic-form-question/dynamic-form-question.component.html
<div [formGroup]="form"> <div [ngSwitch]="question.controlType"> <input *ngSwitchCase="'input'" [formControlName]="question.key" [id]="question.key" [type]="question.type"> <select [id]="question.key" *ngSwitchCase="'select'" [formControlName]="question.key"> <option *ngFor="let opt of question.options" [value]="opt.value">{{opt.key}}</option> </select> </div> </div>
dynamic-form-question/dynamic-form-question.component.ts
import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { QuestionBase } from '../dynamic-form-base/question-base'; @Component({ selector: 'df-question', templateUrl: './dynamic-form-question.component.html' }) export class DynamicFormQuestionComponent { @Input() question: QuestionBase<any>; @Input() form: FormGroup; get isValid() { return this.form.controls[this.question.key].valid } }
从上面的组件能够看出,将来须要添加组件时,只须要添加一种类型,能够用 ngSwitch
决定显示哪一种类型的问题。
在这两个组件中,咱们依赖Angular的formGroup来把模板HTML和底层控件对象链接起来,该对象从问卷问题模型里获取渲染和验证规则。
注意:每一个目录都须要用index.ts
导出模块, 这里须要在theme/components
将咱们注册的组件统一导出。
咱们建立组件以后,须要将咱们的组件注册到 module
中,这里选择 theme/nga.module.ts
注入咱们的组件。
其实使用组件还须要注入
ReactiveFormsModule
, ng2-admin 已经帮咱们注册好了,因此咱们这里只须要注册咱们建立的组件便可
方法以下图,先引入
而后添加至 NGA_COMPONENTS
DynamicForm
指望获得一个问题列表,该列表被绑定到@Input() questions属性。
QuestionService
会返回为工做申请表定义的那组问题列表。在真实的应用程序环境中,咱们会从数据库里得到这些问题列表。
要维护控件,只要很是简单的添加、更新和删除 questions
数组中的对象就能够了。
切换到 pages
文件夹,开始使用咱们上一章建立的 UserAddComponent
pages/user/user-list/user-add/user-add.service.ts
import { Injectable } from '@angular/core'; import { QuestionBase, InputQuestion, SelectQuestion } from '../../../../theme/components/dynamic-form/dynamic-form-base'; @Injectable() export class UserAddService { getQuestions() { let questions: QuestionBase<any>[] = [ new SelectQuestion({ key: 'brave', label: 'Bravery Rating', value: 'solid', options: [ { key: 'Solid', value: 'solid' }, { key: 'Great', value: 'great' }, { key: 'Good', value: 'good' }, { key: 'Unproven', value: 'unproven' } ], order: 3 }), new InputQuestion({ key: 'firstName', label: 'First name', value: 'Bombasto', required: true, order: 1 }), new InputQuestion({ key: 'emailAddress', label: 'Email', type: 'email', order: 2 }) ]; return questions.sort((a, b) => a.order - b.order); } }
须要在 html
文件以及 component
文件中显示,因此修改一下这两个文件
user-add.component.html
<h1> 新增用户组件 </h1> <div class="user-form"> <dynamic-form [questions]="UserAddQuestions"></dynamic-form> </div>
user-add.component.ts
import { Component } from '@angular/core'; import { UserAddService } from './user-add.service'; import { QuestionBase } from '../../../../theme/components/dynamic-form/dynamic-form-base/question-base'; @Component({ selector: 'ngt-user-add', templateUrl: './user-add.component.html', providers: [UserAddService] }) export class UserAddComponent { public UserAddQuestions: QuestionBase<any>[] = []; constructor( private service: UserAddService ) { this.UserAddQuestions = this.service.getQuestions(); } }
根据 Angular 的模块查找规则,因此这里还须要把 NgaModule 注入到 user.module.ts
中,以下图
而后打开浏览器,输入 http://localhost:4200/#/pages/user/list/add
访问效果以下图,填入一些数据,而后点击保存,咱们须要存入的数据,显示在了下方
动态表单的雏形已经作出来了,如今还有几个问题
这些问题咱们会在后续的章节慢慢解决,能够期待。