乐趣区

关于angular:Angular动态表单构建

1、什么是动静表单

动静表单指的是一个能够在运行时依据用户输出或其余动静因素扭转的表单。与传统的动态表单不同,动静表单能够依据不同的条件、选项和输出值来扭转它的外观和行为。

如果你有这样一种表单,其内容必须常常更改以满足疾速变动的业务需要和监管需要,该技术就特地有用。一个典型的例子就是问卷。你可能须要在不同的上下文中获取用户的意见。用户要看到的表单格局和款式应该放弃不变,而你要提的理论问题则会因上下文而异。

2、动静表单的构建思路

之前的表单项是写死的hard-coded,而动静表单中的每个表单项实际上是一个组件,如果须要在某个中央动静的减少或者缩小表单项,那么只须要在此处减少一个实例化的组件或者删除该组件即可。

具体的管制须要一个数据对象数组,通过该数组获取须要展现的多个表单项,进而取得这些表单项组成的表单,再将该表单封装成一个组件,而后在须要调用动静表单的中央插入该组件即可。

3、动静表单具体构建过程

3.1、引入ReactiveFormsModule

因为动静表单基于响应式表单,所以须要在动静表单所在模块引入 ReactiveFormsModule,本文以在根模块创立为例。即在app.module.ts 中引入该模块。


@NgModule({imports: [ BrowserModule, ReactiveFormsModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {constructor() {}}

3.2、创立一个表单对象模型Form

创立 Form 类如下:

export class Form {
  key: string;        // 关键字
  value: string | undefined;    // 值    
  label: string;    // 标签内容
  order: number;    // 程序
  controlType: string;        // 表单项类型(input, dropdown, custom...)
  type: string;        // 具体类型(input 中的 email, number, ...)
  options: {key: string, value: string}[];        // 子项(作用于 controlType 为 dropdown, radio... 时)rule = {} as {        // 验证规定
    required: boolean;
  }

  constructor(options: {
    value?: string;
    key?: string;
    label?: string;
    order?: number;
    controlType?: string;
    type?: string;
    options?: {key: string, value: string}[];
    rule?: {required: boolean}
  } = {}) {
    this.key = options.key || '';
    this.value = options.value;
    this.label = options.label || '';
    this.order = options.order === undefined ? 1 : options.order;
    this.controlType = options.controlType || '';
    this.type = options.type || '';
    this.options = options.options || [];
    this.rule = options.rule ? options.rule : {} as {required: boolean};
  }
}

3.3、创立动静表单模板

有了表单对象模型咱们还须要创立一个放表单项的容器,这里咱们创立一个动静表单模板组件DynamicFormComponent
dynamic-form.component.ts

import {Component, Input, OnInit} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {Form} from '../Form';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
})
export class DynamicFormComponent implements OnInit {

  formGroup!: FormGroup
  @Input() forms: Form[] = [];

  constructor() {}

  ngOnInit(): void {}

  onSubmit(): void {console.log('onSubmit', this.formGroup.value);
  }
}

dynamic-form.component.html

<div>
  <form (ngSubmit)="onSubmit()" [formGroup]="formGroup">

    <div *ngFor="let form of forms" class="form-row">
      <!-- 表单项 [formGroup]="formGroup" [form]="form" -->
    </div>

    <div class="form-row">
      <button type="submit" [disabled]="!formGroup.valid"> 保留 </button>
    </div>
  </form>
</div>

3.4、获取数据

当初咱们曾经有了 Form 类, 以及放表单项的容器了,接下来咱们创立服务类 form.service.ts 来获取数据,此处咱们应用模仿数据,内容如下:

import {Injectable} from '@angular/core';
import {Observable, of} from 'rxjs';
import {Form} from '../Form';

@Injectable({providedIn: 'root'})
export class FormService {constructor() { }

  getForms(): Observable<Form[]> {const forms: Form[] = [

      new Form({
        key: 'age',
        label: '年龄',
        controlType: 'select',
        options: [{key: '17', value: '18'},
          {key: '18', value: '19'},
          {key: '19', value: '20'},
        ],
        rule: {required: true}
      }),

      new Form({
        key: 'name',
        label: '姓名',
        controlType: 'textbox',
        type: 'text',
        rule: {required: true}
      }),

      new Form({
        key: 'phone',
        label: '手机号',
        controlType: 'textbox',
        type: 'number',
      })
    ];
    return of(forms.sort((a, b) => a.order - b.order));
  }
}


3.5、编写表单组

当初咱们能够通过下面的服务类获取数据失去 forms: Form[],接下来咱们要实现通过forms 转换失去 formGroup: FormGroup。在dynamic-form.component.ts 中,创立办法 toFormGroup() 来在组件通过 ngOninit() 初始化时将 forms 转换成 formGroup(该办法也能够独自建设一个服务来实现),此时能够通过Form 类的 rule 属性进行校验规定的设定。
批改完后 dynamic-form.component.ts 代码如下:

import {Component, Input, OnInit} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {Form} from '../Form';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
})
export class DynamicFormComponent implements OnInit {

  formGroup!: FormGroup
  @Input() forms: Form[]|null = [];

  constructor() {}

  ngOnInit(): void {this.formGroup = this.toFormGroup(this.forms as Form[]);
  }

  onSubmit(): void {console.log('onSubmit', this.formGroup.value);
  }

  toFormGroup(forms: Form[] ) {const group: any = {};

    forms.forEach(form => {group[form.key] = form.rule.required ?
        new FormControl(form.value || '', Validators.required):
        new FormControl(form.value || '');
    });
    return new FormGroup(group);
  }
}

3.6、编写动静表单内容

既然曾经有了容器,咱们就能够放咱们想要的内容了,也就是表单项,接下来咱们创立一个组件DynamicFormUnitComponent,来使得每一个该组件实例能够匹配一个表单项。
dynamic-form-unit.component.ts

import {Component, Input, OnInit} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {Form} from '../Form';

@Component({
  selector: 'app-dynamic-form-unit',
  templateUrl: './dynamic-form-unit.component.html',
})
export class DynamicFormUnitComponent implements OnInit {@Input() formGroup!: FormGroup;
  @Input() form!: Form;

  constructor() {}

  ngOnInit(): void {}

  get isValid() { return this.formGroup.controls[this.form.key].valid; }
}

dynamic-form-unit.component.html

<div [formGroup]="formGroup" [ngSwitch]="form.controlType">

  <ng-container *ngSwitchCase="'textbox'">
    <label [for]="form.key">{{form.label}}</label>
    <input [formControlName]="form.key"
           [id]="form.key" [type]="form.type">
  </ng-container>

  <ng-container *ngSwitchCase="'dropdown'">
    <label [for]="form.key">{{form.label}}</label>
    <select [formControlName]="form.key" [id]="form.key">
      <option *ngFor="let opt of form.options" [value]="opt.key"> {{opt.value}}</option>
    </select>
  </ng-container>

  <ng-container *ngSwitchCase="'checkbox'">
    <label>
      {{form.label}}
      <input type="checkbox" [name]="form.key" [formControlName]="form.key" [value]="form.value"/>
    </label>
  </ng-container>

  <ng-container *ngSwitchCase="'radio'">
    <h3>{{form.label}}</h3>
    <label *ngFor="let option of form.options">
      <input type="radio"
             [name]="option.key"
             [formControlName]="option.key"
             [value]="option.value"
      >
      {{option.key}}
    </label>
  </ng-container>

  <div style="color: red" *ngIf="!isValid">{{form.label}}不能为空 </div>
</div>

该组件创立实现之后从新回到 dynamic-form.component.html 文件找到

<div *ngFor="let form of forms" class="form-row">
  <!-- 表单项 [formGroup]="formGroup" [form]="form" -->
</div>

批改成

<div *ngFor="let form of forms" class="form-row">
  <app-dynamic-form-unit [formGroup]="formGroup" [form]="form"></app-dynamic-form-unit>
</div>

此时动静表单就构建实现了,接下来咱们去查看成果。

3.7、显示表单

要显示动静表单的一个实例,只须要在 AppComponent 中退出 <app-dynamic-form> 标签,AppComponent外壳模板会把一个 FormService 返回的 forms 数组传给表单容器组件 <app-dynamic-form>
app.component.ts

import {Component} from '@angular/core';
import {FormService} from './dynamic-form/service/form.service';
import {Observable} from 'rxjs';
import {Form} from './dynamic-form/Form';

@Component({
  selector: 'app-root',
  template: `
    <div>
      <h2> 请输出相干信息 </h2>
      <app-dynamic-form [forms]="forms$ | async"></app-dynamic-form>
    </div>`,
})
export class AppComponent {forms$: Observable<Form[]>;

  constructor(private formService: FormService) {this.forms$ = this.formService.getForms();
  }

}

效果图:

4、改良

4.1、构建组件

以后 dynamic-form-unit.component.html 的内容:

<div [formGroup]="formGroup" [ngSwitch]="form.controlType">

  <ng-container *ngSwitchCase="'textbox'">
    <label [for]="form.key">{{form.label}}</label>
    <input [formControlName]="form.key"
           [id]="form.key" [type]="form.type">
  </ng-container>

  <ng-container *ngSwitchCase="'dropdown'">
    <label [for]="form.key">{{form.label}}</label>
    <select [formControlName]="form.key" [id]="form.key">
      <option *ngFor="let opt of form.options" [value]="opt.key"> {{opt.value}}</option>
    </select>
  </ng-container>

  <ng-container *ngSwitchCase="'checkbox'">
    <label>
      {{form.label}}
      <input type="checkbox" [name]="form.key" [formControlName]="form.key" [value]="form.value"/>
    </label>
  </ng-container>

  <ng-container *ngSwitchCase="'radio'">
    <h3>{{form.label}}</h3>
    <label *ngFor="let option of form.options">
      <input type="radio"
             [name]="option.key"
             [formControlName]="option.key"
             [value]="option.value"
      >
      {{option.key}}
    </label>
  </ng-container>

  <div style="color: red" *ngIf="!isValid">{{form.label}}不能为空 </div>
</div>

能够看到该模板文件中每一个 <ng-container></ng-container> 中对应了一种表单项类型,咱们对每一项构建一个组件从而不便代码的保护,也不便代码测试以及重用构建不同的模板。

抽离组件、勾销 formGroup 的 input、勾销 ngSwitch

5、总结

参考文章:
1.https://angular.cn/guide/dynamic-form#display-the-form
2.https://www.danywalls.com/creating-dynamic-forms-in-angular-a…

继续更新中

退出移动版