独角兽企业重金招聘Python工程师标准>>>
看了两天官网文档表单和表单验证后,才发现之前自己仅仅通过特定的 Angular CSS 类去控制去反馈用户的操作,如ng-touched等等是多么的入门,虽然说这已经够用了,但是之前写的都是在视图层上写逻辑,各种判断,导致后期维护有点困难。趁有时间记录一下这两天看了资料的一些心得。
模板驱动表单VS响应式表单
两者都可以通过表单绑定获取整个表单的值和是否合法eg: myForm.value, myForm.valid等等
Template-Driven Forms (模板驱动表单) 的特点
使用方便
适用于简单的场景
通过 [(ngModel)] 实现数据双向绑定
最小化组件类的代码
不易于单元测试
导入FormModule
表单绑定通过#myForm="ngForm"
Reactive Forms (响应式表单) 的特点
比较灵活
适用于复杂的场景
简化了HTML模板的代码,把验证逻辑抽离到组件类中
方便的跟踪表单控件值的变化
易于单元测试
导入ReactiveFormsModule
表单绑定通过[formGroup]="myForm"
组件类代码解释
Template-Driven Forms (模板驱动表单)
import { Component, AfterViewChecked, ViewChild } from '@angular/core';import { NgForm } from '@angular/forms';import { Hero }from '../shared/hero';@Component({selector: 'hero-form-template2',templateUrl: './hero-form-ponent.html'})export class HeroFormTemplate2Component implements AfterViewChecked {powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');submitted = false;onSubmit() {this.submitted = true;}// Reset the form with a new hero AND restore 'pristine' class state// by toggling 'active' flag which causes the form// to be removed/re-added in a tick via NgIf// TODO: Workaround until NgForm has a reset method (#6822)active = true;addHero() {this.hero = new Hero(42, '', '');this.active = false;setTimeout(() => this.active = true, 0);}/*组件手动记录表单对象*/heroForm: NgForm;/*heroFrom变量是Angular从模板衍生出来的控制模型的引用。 我们利用@ViewChild来告诉Angular注入这个模型到组件类的currentForm*/@ViewChild('heroForm') currentForm: NgForm;/*监听视图中表格变化,第一次渲染表格对象有多少个值就跑多少次*/ngAfterViewChecked() {this.formChanged();}formChanged() {/*检测是否发生变化*/if (this.currentForm === this.heroForm) { return; }this.heroForm = this.currentForm;if (this.heroForm) {/*heroForm得到的是FormModule对象,该对象继承于AbstractControl抽象类,里面包含touched等*//*valueChanges返回一个Observal,用于获取视图返回的可观察对象*/this.heroForm.valueChanges.subscribe(data => this.onValueChanged(data));}}onValueChanged(data?: any) {if (!this.heroForm) { return; }const form = this.heroForm.form;for (const field in this.formErrors) {// clear previous error message (if any)this.formErrors[field] = '';/*heroForm.form.get('name')获取对应表单里的键值*/const control = form.get(field);if (control && control.dirty && !control.valid) {const messages = this.validationMessages[field];for (const key in control.errors) {this.formErrors[field] += messages[key] + ' ';}}}}formErrors = {'name': '','power': ''};validationMessages = {'name': {'required':'Name is required.','minlength':'Name must be at least 4 characters long.','maxlength':'Name cannot be more than 24 characters long.','forbiddenName': 'Someone named "Bob" cannot be a hero.'},'power': {'required': 'Power is required.'}};}
Reactive Forms (响应式表单) 的特点
import { Component, OnInit } from '@angular/core';import { FormGroup, FormBuilder, Validators } from '@angular/forms';import { Hero } from '../shared/hero';import { forbiddenNameValidator } from '../shared/forbidden-name.directive';@Component({...})export class HeroFormReactiveComponent implements OnInit {.../**/heroForm: FormGroup;constructor(private fb: FormBuilder) { }ngOnInit(): void {/*初始化表单对象*/this.buildForm();}buildForm(): void {/*FormBuilder.group是一个用来创建FormGroup的工厂方法*/this.heroForm = this.fb.group({/*这里的值接受两个参数,第一个是值,第二个是检验器,如果有多个检验器必须用数组表示*/'name': [this.hero.name, [Validators.required,Validators.minLength(4),Validators.maxLength(24),forbiddenNameValidator(/bob/i)]],'alterEgo': [this.hero.alterEgo],'power': [this.hero.power, Validators.required]});this.heroForm.valueChanges.subscribe(data => this.onValueChanged(data));this.onValueChanged(); // (re)set validation messages now}/*与驱动表单上面代码类似*/...}
组件视图层对比
Template-Driven Forms (模板驱动表单)
表组通过ngModelGroup去绑定
<form #templateForm="ngForm" novalidate (ngSubmit)="onSubmit(templateForm)"><span>Name</span><label class="user_name"><input #uName="ngModel" type="text" name="user_name" [(ngModel)]="user.name" required></label><div ngModelGroup="account"><span>QQ</span><label class="user_name"><input type="text" name="user_qq" [(ngModel)]="user.account.qq" required></label><span>Password</span><label class="user_name"><input type="password" name="user_password" [(ngModel)]="user.account.password" required></label></div><button style="width: 200px; height: 36px; border: 1px solid #ccc; margin-top: 20px;" [disabled]="templateForm.invalid">提交</button></form>
Reactive Forms (响应式表单) 的特点
表组通过
formGroupName="address"
-------------------------------------------------------------------
自定义表单验证
html:
这里注意要通过表单.get的方法获取FormModel的东西,reactiveForm.get('age').touched 或者reactive.controls.age?.touched
<form [formGroup]="reactiveForm" (ngSubmit)="onSubmit()"><div class="form-group"><label for="name">Name</label><input type="text" id="name" class="form-control" formControlName="name"></div><div class="form-group" [ngClass]="{'has-error': (reactiveForm.get('age').touched || reactiveForm.get('age').dirty) && !reactiveForm.get('age').valid }"><label class="col-md-2 control-label" for="ageId">年龄</label><div class="col-md-8"><input class="form-control" id="ageId" type="number" placeholder="请输入年龄" formControlName="age" /><span class="help-block" *ngIf="(reactiveForm.get('age').touched || reactiveForm.get('age').dirty) && reactiveForm.get('age').errors"><span *ngIf="reactiveForm.get('age').errors.range">输入年龄不合法</span></span></div></div><p>{{reactiveForm.value | json}}</p></form>
ts:
...@Component({...})export class ReactiveFormComponent implements OnInit {user: any = {name: '',age: 0};reactiveForm: FormGroup;constructor(public fb: FormBuilder) { }ngOnInit() {this.buildForm();}buildForm(): void {this.reactiveForm = this.fb.group({'name': [this.user.name, Validators.required],'age': [this.user.age, this.ageValidator] //自定义验证方法使用});// this.reactiveForm.valueChanges// .subscribe(data => { console.log(data) });}/*有参数的验证器工厂函数写法,这里FormControl和下面的AbstractControl都是指向抽象类的属性和方法都是为了获取表单属性*/private ageValidatorParams(min: number, max: number) {return (c: FormControl): { [key: string]: any } | null => {let age = c.value;if (age && (isNaN(age) || age < min || age > max)) {return { 'range': true, min: min, max: max };}return null;}}/*必须返回一个对象,该对象为错误信息的值*/private ageValidator(c: AbstractControl): { [key: string]: any } | null {let age = c.value;if (age && (isNaN(age) || age < 20 || age > 120)) {return { 'range': true, min: 20, max: 120 };}return null;}}
参考文献:
1、Angular官方文档(表单验证)
2、模板驱动式表单
3、响应式表单
4、案例情况比较多的表单验证