9

在使用ng-zorro的表单时,发现他和angular的表单有很大不同,于是就去学习了一下angular的表单。
在angular中表单有两种形式,一种是模板驱动表单,一种是响应式表单,模板驱动表单跟angularjs的表单差不多,都是在模板中进行数据绑定,验证等,而响应式表单是功能更强大,灵活的表单形式。

FormControl表示表单控件

响应式表单是用模型驱动来处理表单与用户交互的,在响应式表单中每个表单控件都是一个模型。使用响应式表单时,需要在导入@angular/forms 包中导入 ReactiveFormsModule并在模块中加入imports数组中.

clipboard.png

创建一个表单控件

在组件类中导入FormControl类,创建一个FromControl类实例,他将关联到表单中的一个控件.

clipboard.png'

之后在模板中用fromControl指令把模板中表单控件关联到name对象:``

<label>
  Name:
  <input class="form-control" type="text" [formControl]="name">
</label>
<p>
  {{name.value}}
</p>

clipboard.png

这个时候我们的name对象就关联到了模板中的input表单控件了,用name.value属性就可以看到对象的值,他是与视图值绑定在一起的.此时这个input控件就由name这个模型管理,获取值,修改值,验证都通过这个模型进行.

FolmGroup管理FormContrl

将控件合并

在表单中,通常有多个控件,把多个控件合并在一起有助于管理,可以用FormGroup来管理控件FormContrl.
从@angular/forms包中导入FormGroup,新建一个FormGroup对象,在构造函数中传入一个对象,属性名代表控件的名字,值就是一个FormContrl实例.

clipboard.png

关联FormGroup模型到模板视图

在模板的表单中用FormGroup指令来关联模型,由 FormControlName指令提供的formControlName属性把每个输入框和 FormGroup 中定义的表单控件绑定起来。这样在视图表单控件值修改时,会反应到FormGroup上.

<form [formGroup]="teacher">
<label>
  TeacherName:
  <input class="form-control" type="text" formControlName="name">
</label>
  <label>
    TeacherEmail:
    <input class="form-control" type="text" formControlName="email">
  </label>
</form>
<pre>{{teacher.value|json}}</pre>

clipboard.png

用FormBuilder简化生成控件

因为表单使用很频繁,手动创建多个表单控件会非常繁琐,可以使用FoRmBuilder服务来简化创建过程:
导入@angular/forms 包中导入 FormBuilder类,在构造函数中注入服务,使用服务方法来简化生成过程:

constructor(private fb: FormBuilder) {
  }

生成对比:

name = new FormControl('');
  builderName = this.fb.control('');

  teacher = new FormGroup({
    name: new FormControl(''),
    email: new FormControl('')
  });

  builderTeacher = this.fb.group({
    name: [''],
    email: ['']
  });

动态表单

在之前使用angularjs开发时,很多表单都很相似,但是却不得不写多个相似的表单,使用响应式表单可以将这些表单都抽象出来,动态生成表单,使得不用写重复的代码,而且更易于维护。
先比较这两个控件,一个input一个select下拉框:

clipboard.png

这两个控件有很多的共同点,他们都是html标签,都有一个label,一个id,一个formcontrolName,一个type,且控件都有值(.value),只是他们的值都是不同的,可以把这两个控件抽象成一个基类对象,他有id,lable,html标签类型,value属性,在通过继承基类对象生成对应的控件.

构建对象模型

基类对象

  value: T;                 //控件的值    
  key: string;              //控件名字
  label: string;            //控件描述
  controlType: string;      //html标签类型
constructor(options: {
    value?: T,
    key?: string,
    label?: string,
    controlType?: string
  } = {}) {
    this.value = options.value;
    this.key = options.key || '';
    this.label = options.label || '';
    this.controlType = options.controlType || '';
  }

input对象

import {BaseOb} from './base-ob';

export class InputText extends BaseOb{
  controlType = 'input';
  type: string;

  constructor(options: {} = {}) {
    super(options);
    this.type = options['type'] || '';
  }
}

通过继承BaseOb对象,并声明自己为input(html类型),加上一个type(text)

select对象:

import {BaseOb} from './base-ob';

export class Select extends BaseOb{
  controlType = 'select';
  options: {key: string, value: string}[] = [];

  constructor(options: {} = {}) {
    super(options);
    this.options = options['options'] || [];
  }
}

select不需要type类型,但他需要一个options来循环生成下拉框

将对象模型数组转化为同一个FormGroup

我们需要把这个input和select转化为一个FormGroup来与模板视图交互,因此需要一个服务把BaseOb数组转化为一个FormGroup对象:

import { Injectable } from '@angular/core';
import {BaseOb} from './base-ob';
import {FormControl, FormGroup} from '@angular/forms';

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

  constructor() { }

  toFormGroup(obs: BaseOb<any>[] ) {
    const group: any = {};

    obs.forEach(ob => {
      group[ob.key] = new FormControl(ob.value || '');    // 以key作为FormControl的名字,value作为值
    });
    
    return new FormGroup(group);                         // 将对象传入formGroup构造函数生成FormGroup
  }
}

将模型对象转化为模板视图

有了模型对象,就得把模型对象转化为之前的视图,用一个组件来做这件事:

import {Component, Input, OnInit} from '@angular/core';
import {BaseOb} from '../base-ob';
import {FormGroup} from '@angular/forms';

@Component({
  selector: 'app-element',
  templateUrl: './element.component.html',
  styleUrls: ['./element.component.css']
})
export class ElementComponent implements OnInit {
  @Input() element: BaseOb<any>;
  @Input() form: FormGroup;
  constructor() { }

  ngOnInit() {
  }

}

这个组件类首先接受一个抽象的基类对象和一个FormGoup,用Input()获取,然后再在模板中根据element生成相应的控件:

<div [formGroup]="form">
  <label [attr.for]="element.key">{{element.label}}</label>

  <div [ngSwitch]="element.controlType">

    <input *ngSwitchCase="'input'" [formControlName]="element.key"
           [id]="element.key" [type]="element.type">

    <select [id]="element.key" *ngSwitchCase="'select'" [formControlName]="element.key">
      <option *ngFor="let opt of element.options" [value]="opt.key">{{opt.value}}</option>
    </select>
  </div>
</div>

用ngSwitch来判断,如果当前的控件html属性为input显示<input>,为select显示<select>,其余如此.

使用动态表单

通过生成BaseOb数组并用ngfor循环生成<app-element>就可以动态的生成表单控件了:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  obs: BaseOb<any>[];
  form: FormGroup;

  constructor(private obService: BaseObService) {  }

  ngOnInit(): void {
    this.obs = [
      new InputText({
        key: 'name',
        label: 'teacher name',
        value: 'zhansan',
        type: 'text'
      }),
      new Select({
        key: 'klass',
        label: 'klasses',
        options: [
          {key: '1',  value: '一班'},
          {key: '2',  value: '二班'},
          {key: '3',   value: '三班'},
          {key: '4', value: '四班'}
        ]
      })
    ];
    this.form = this.obService.toFormGroup(this.obs);
  }
}

在组件中新建一个input(text)模型和一个select模型,通过服务获取表单组,之后在组件模板中调用<app-element>

<h2>动态表单生成:</h2>
<div style="width: 200px;">
  <form [formGroup]="form">

    <div *ngFor="let o of obs">
      <app-element [element]="o" [form]="form"></app-element>
    </div>
  </form>
</div>
<pre>{{form.value|json}}</pre>

循环obs数组,app-element组件会根据遍历对象生成相应的控件并绑定.
效果:

clipboard.png]
可以用服务来生成BaseOb对象数组来定义我们需要的表单控件,验证信息也可以通过这样类似的方法生成,只需要提供表单控件各自的属性,统一生成表单控件,便于维护和编写。


鲸冬香
456 声望27 粉丝