前言
表单在整个系统中的作用相当重要,这里主要扯下响应表单的实现方式。
首先需要操作表单的模块引入这两个模块;
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
表单控件响应的几种状态
模板驱动表单依赖FormsModule
,
数据驱动的表单依赖FormsModule,ReactiveFormsModule
一般做表单校验及操作推荐用数据驱动的方式,好维护和理解。
模板驱动
模板驱动:主要是依赖[(ngModel)]
和#scope_var
以及原生表单控件属性(require
,minlenght
,maxlength
等)来操作表单的那的值亦或者校验
- 一个最简单的例子
<!--#UserName 是局部变量,若是有ngmodel,拿到的就是一个响应对象,若是非ngmodel绑定的,则是dom元素代码--> <!--testform这个局部变量保存了表单的所有相关信息--> <!--ngSubmit是用来触发表单提交的--> <!--ngModel相应变量的值--> <!--$event是原生dom对象--> <form #testform="ngForm" (ngSubmit)="Submit(testform.value,testform.valid)"> <label for="username">Name</label> <input type="text" id="username" #UserName="username" class="form-control" required minlength="4" maxlength="24" name="username" [(ngModel)]="username" [ngModelChange]="validate($event)"> <div *ngIf="UserName.valid || (UserName.pristine && !testform.submitted)"> 您输入的值有误,请重新输入 </div> <button type="submit" >提交</button> </form>
有两种方式处理来对上面的表单做校验;
- 在
Submit()
函数内,在点击提交的时候对整个表单一一去判断,传统方式基本这样
- 每个控件输入的时候对应去触发对应的事件做校验,比如
[ngModelChange]
来处理双向绑定的值校验
数据驱动(Reactive Form)
响应式表表单:原理是一开始就构建整个表单,表单的值通过特殊指令formControlName
一一关联(类似ngModel
);
相关名词:
FormGroup
: 用来追踪表单控件有效状态及值 =》 可以理解为获取且可以操作整个表单的数据FormBuilder
:表单数据构建工具[构建初始表单],简化构建代码(包括了new FormGroup()
,new FormControl()
,new FormArray()
),FormGroup()
内置多种校验方式formControlName
: 同步与FormGroup
构建表单内相同字段的值!
项目中的案例
html
<div [@flyIn]="true"> <div class="beautify-form" *ngIf="!showLoading"> <div class="page-header"> 欢迎登录 </div> <form [formGroup]="form" (ngSubmit)="onSubmit(form)"> <div class="form-group" [ngClass]="{ 'has-danger': form.controls.UserName.invalid && form.controls.UserName.value ,'has-success': form.controls.UserName.valid && form.controls.UserName.value }"> <div class="input-group input-group-lg"> <span class="input-group-addon fpd fpd-ordinarylogin1"></span> <input type="text" class="form-control" formControlName="UserName" placeholder="手机号码 \ 邮箱 "> </div> <div class="form-control-feedback" *ngIf="(form.controls.UserName.dirty || form.controls.UserName.pristine) && form.controls.UserName.invalid && form.controls.UserName.value">账号不符合规范</div> <div class="form-control-feedback" *ngIf="(form.controls.UserName.dirty || form.controls.UserName.pristine) && form.controls.UserName.valid && form.controls.UserName.value">账号符合规范</div> </div> <div class="form-group" [ngClass]="{ 'has-danger': form.controls.PassWord.invalid && form.controls.PassWord.value ,'has-success': form.controls.PassWord.valid && form.controls.PassWord.value }"> <div class="input-group input-group-lg"> <span class="input-group-addon fpd fpd-mima"></span> <input type="PassWord" class="form-control" formControlName="PassWord" placeholder="请输入密码"> </div> <div class="form-control-feedback" *ngIf="(form.controls.PassWord.dirty || form.controls.PassWord.pristine) && form.controls.PassWord.invalid && form.controls.PassWord.value ">密码不符合规范,请重新输入</div> <div class="form-control-feedback" *ngIf="(form.controls.PassWord.dirty || form.controls.PassWord.pristine) && form.controls.PassWord.valid && form.controls.PassWord.value ">密码符合规范</div> </div> <div class="form-group "> <div class="flex"> <div class="beautify-wrap flex-wrap"> <input type="checkbox" class="beautify-checkbox" name="rememberme" id="rememberAccount" formControlName="rememberAccount"> <label for="rememberAccount"></label>记住账号 </div> <!--<a [routerLink]="['/account/reset-pw']">忘记密码</a>--> </div> </div> <div class="message-tips" *ngIf="messageTips"> <i class="fpd fpd-error"></i> {{messageTips}} </div> <div class="form-group "> <button class="btn btn-lg btn-outline-success btn-block" type="submit" [disabled]="form.invalid">登录</button> </div> <div class="form-group"> <span class="noaccount-notify">没有账号?点击</span><a [routerLink]="['/account/collect']" class="collect-user">用户登记</a> </div> </form> </div> <div class="loading" *ngIf="showLoading"> <app-mit-loading [option]="'load4'"></app-mit-loading> </div> </div>
component.ts
import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormGroup, Validators, FormBuilder } from '@angular/forms'; // 引入表单的一些特性 import { Router } from '@angular/router'; import { AccountService } from '../../services/account.service'; import { environment } from '../../../../../environments/environment'; import { flyIn } from '../../../../animation/flyIn'; import { Observable } from 'rxjs/Observable'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.scss'], animations: [flyIn] }) export class LoginComponent implements OnInit, OnDestroy { public form: FormGroup; // 表单对象 public showLoading = false; public messageTips: string; public login_subscribe: any; // Validators的写法注意事项 // v2.x版本这样的写法是可行的,v4有调整,不然不会生效 // 'UserName':'', [ Validators.compose([Validators.minLength(6)] // v4+ , 第一位的''代表这个元素初始化构建为空值,类似未输入状态 // 'UserName': ['', Validators.compose([Validators.minLength(6)] // Validators可选参数 // 1. required :必须验证的,返回布尔值 // 2. minLength : 最小长度 // 3. maxLenght: 最大长度 // 4. nullValidator : 空值判断 // 5. coompose :多重判断组合,下面有写法 // 6. pattern是支持正则模式,正则谨记转义转义转义 constructor(private fb: FormBuilder, private router: Router, private account: AccountService) { this.form = fb.group({ 'UserName': ['', Validators.compose([Validators.minLength(6) || Validators.pattern('(0|86|17951)?(-)?1[3,4,5,7,8,9]\\d{9}') || Validators.pattern('[\\.a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+')])], 'PassWord': ['', Validators.compose([Validators.required, Validators.pattern('\\w{8,16}')])], 'rememberAccount': [''] }); } ngOnInit() { } // 登录事件 onSubmit(e) { this.showLoading = true; this.login_subscribe = this.account.login(e.value).subscribe((res) => { console.log('省略。。。。。。') }, (err) => { this.showLoading = false; }); } ngOnDestroy() { if (this.login_subscribe) { this.login_subscribe.unsubscribe(); } } }
效果图
嵌套表单
有些时候我们接口数据层次不可能只有一层,出现两层三层都有可能,这时候需要我们构建一个嵌套表单。。。
html
- v2-的写法:表单的取值可以用
controls
直接点出来
<div class="custom-card"> <div class="custom-card-body"> <form [formGroup]="form" (ngSubmit)="onSubmit(form.value)"> <div class="row" formGroupName="RuleContent"> <div class="col-sm-12 col-md-12 col-lg-8 offset-lg-2"> <div class="form-group row" [ngClass]="{ 'has-danger': form.controls.RuleContent.controls.FenceName.invalid && form.controls.RuleContent.controls.FenceName.value ,'has-success': form.controls.RuleContent.controls.FenceName.valid && form.controls.RuleContent.controls.FenceName.value }"> <label tooltip="" class="col-sm-10 col-md-3 form-control-label col-lg-3 star">速度栅栏名称</label> <div class="col-sm-8 col-md-6 col-lg-6"> <input type="text" class="form-control" formControlName="FenceName" placeholder="栅栏名称"> </div> <div class="col-2 col-sm-4 col-lg-3 flex-align-center"> 不超过十个字 </div> </div> <div class="form-group row" [ngClass]="{ 'has-danger': form.controls.RuleContent.controls.MaxSpeed.invalid && form.controls.RuleContent.controls.MaxSpeed.value ,'has-success': form.controls.RuleContent.controls.MaxSpeed.valid && form.controls.RuleContent.controls.MaxSpeed.value }"> <label tooltip="" class="col-sm-10 col-md-3 form-control-label col-lg-3 star">速度阈值</label> <div class="col-sm-8 col-md-6 col-lg-6"> <input type="number" class="form-control" min="1" formControlName="MaxSpeed" placeholder="整数"> </div> <div class="col-2 col-sm-4 col-lg-3 flex-align-center"> km/h </div> </div> <div class="form-group row"> <div class="col-12 col-sm-10 col-md-6 offset-sm-2 offset-md-4 offset-lg-3"> <button type="submit" class="btn btn-primary" [disabled]="form.invalid">保存</button> <button type="button" class="btn btn-secondary" (click)="back()">取消</button> </div> </div> </div> </div> </form> </div> </div>
- v4+的写法 :嵌套表单的取值必须用
.get()
来获取,不然会报错误,具体原因是api改动了,看下官方文档就知道,改动了挺多(不仅仅这块)
<div class="custom-card"> <div class="custom-card-body"> <form [formGroup]="form" (ngSubmit)="onSubmit(form.value)"> <div class="row" formGroupName="RuleContent"> <div class="col-sm-12 col-md-12 col-lg-8 offset-lg-2"> <div class="form-group row" [ngClass]="{ 'has-danger': form.get('RuleContent.FenceName').invalid && form.get('RuleContent.FenceName').value ,'has-success': form.get('RuleContent.FenceName').valid && form.get('RuleContent.FenceName').value }"> <label tooltip="" class="col-sm-10 col-md-3 form-control-label col-lg-3 star">速度栅栏名称</label> <div class="col-sm-8 col-md-6 col-lg-6"> <input type="text" class="form-control" formControlName="FenceName" placeholder="栅栏名称"> </div> <div class="col-2 col-sm-4 col-lg-3 flex-align-center"> 不超过十个字 </div> </div> <div class="form-group row" [ngClass]="{ 'has-danger': form.get('RuleContent.MaxSpeed').invalid && form.get('RuleContent.MaxSpeed').value ,'has-success': form.get('RuleContent.MaxSpeed').valid && form.get('RuleContent.MaxSpeed').value }"> <label tooltip="" class="col-sm-10 col-md-3 form-control-label col-lg-3 star">速度阈值</label> <div class="col-sm-8 col-md-6 col-lg-6"> <input type="number" class="form-control" min="1" formControlName="MaxSpeed" placeholder="整数"> </div> <div class="col-2 col-sm-4 col-lg-3 flex-align-center"> km/h </div> </div> <div class="form-group row"> <div class="col-12 col-sm-10 col-md-6 offset-sm-2 offset-md-4 offset-lg-3"> <button type="submit" class="btn btn-primary" [disabled]="form.invalid">保存</button> <button type="button" class="btn btn-secondary" (click)="back()">取消</button> </div> </div> </div> </div> </form> </div> </div>
components.ts
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; // 引入表单的一些特性 // 动画 import { fadeIn } from '../../../../../animation/fadeIn'; // 服务 import { SpeedFenceService } from '../speed-fence.service'; import { EventsService } from '../../../../../services/events-service.service'; @Component({ selector: 'app-modify', templateUrl: './modify.component.html', styleUrls: ['./modify.component.scss'], animations: [fadeIn] }) export class ModifyComponent implements OnInit { public form: FormGroup; public getId: any; public id: number; constructor( private speedFenceService: SpeedFenceService, private eventsService: EventsService, private router: Router, private activatedRoute: ActivatedRoute, private fb: FormBuilder ) { this.form = fb.group({ 'ID': 0, 'RuleContent': this.fb.group({ 'MaxSpeed': [0, Validators.compose([Validators.required, Validators.pattern('(([4-9][0-9])|(1[0-1][0-9])|(120))')])], 'FenceName': ['', Validators.compose([Validators.required, Validators.minLength(2), Validators.maxLength(10)])], }) }); } ngOnInit() { this.checkAction(); // console.log(this.form); } // 获取ID checkAction() { this.activatedRoute.params.subscribe((params: { id: string }) => { console.log(params); if (params.id) { console.log(this.id); this.id = parseInt(params.id, 10); this.form.controls['ID'].setValue(this.id); this.GetSpeedFenceSettingByFenceId({ FenceId: parseInt(params.id, 10) }); } }); } GetSpeedFenceSettingByFenceId(data) { this.speedFenceService.GetSpeedFenceSettingByFenceId(data).subscribe( res => { if (res.State) { this.form.controls['RuleContent'].setValue({ 'MaxSpeed': res.Data.RuleContent.MaxSpeed || '', 'FenceName': res.Data.RuleContent.FenceName || '', }); } }, err => { } ); } onSubmit(form) { console.log('此处省略。。。。。'); } // 取消 back() { if (this.id) { this.router.navigate(['../../'], { relativeTo: this.activatedRoute }); } else { this.router.navigate(['../'], { relativeTo: this.activatedRoute }); } } }