Angular 表单
问题描述
Web
开发中,表单一直是一个重要的话题。
在AngularJS
中,我们可以使用双向数据绑定很简单地完成表单的开发,但是会带来严重的性能问题,而Angular
对于表单的设计,让我们的表单在保持性能的同时更优雅。
实例
我们以一个最简单的登录表单为例来学习Angular
中的表单:
思想
这就是Angular
的表单思想,一个FormGroup
管理整个表单,同时FormControl
管理表单内的各个元素。
Create
导入表单模块:
基础的HTML
表单代码:
<div class="container"> <div class="row"> <div class="col-md-4 col-md-offset-4"> <h2 class="text-center">Angular Form</h2> <form> <div class="form-group"> <label>Email address</label> <input type="email" class="form-control" placeholder="Email" /> </div> <div class="form-group"> <label>Password</label> <input type="password" class="form-control"placeholder="Password" /> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div> </div>
略加修改:
<div class="container"> <div class="row"> <div class="col-md-4 col-md-offset-4"> <h2 class="text-center">Angular Form</h2> <form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)"> <div class="form-group"> <label>Email address</label> <input type="email" class="form-control" placeholder="Email" ngModel /> </div> <div class="form-group"> <label>Password</label> <input type="password" class="form-control"placeholder="Password" ngModel /> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div> </div>
当我们的应用导入FormsModule
时,form
就不再是原生的form
了,而是Angular
重写过的NgForm
组件,就像在AngularJS
中使用的form
其实是被框架扩展的指令。
所以我们可以为form
添加NgForm
组件定义的输入输出。
这是NgForm
的官方api
文档描述,导出NgForm
,然后输出ngSubmit
事件。
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)"> </form>
这里用到了导出的NgForm
,并为其取名为myForm
,所以传给onSubmit
的myForm
其实就是导出的NgForm
,当业务逻辑非常简单的情况下可以这样写,直接用导出的NgForm
,就相当于使用一个默认配置好的对象。
<input type="email" class="form-control" name="email" placeholder="Email" ngModel /> <input type="password" class="form-control" name="password" placeholder="Password" ngModel />
两个input
用到了ngModel
指令,该指令会为该元素创建一个默认的FormControl
。
onSubmit(myForm: NgForm): void { console.log(myForm); console.log(myForm.value); }
提交表单,打印,可以查看myForm
中的许多属性,同时它的value
属性就是表单内容。
点开NgForm
,其实除了表单的值外,还有dirty
、error
、invalid
等属性方便我们对表单进行验证。
点开controls
属性,我们可以看到该表单中的FormControl
:
<input type="email" class="form-control" name="email" placeholder="Email" /> <input type="password" class="form-control" name="password" placeholder="Password" />
我们尝试将input
中原来添加的ngModel
指令删除,再打印。可以看到该表单中没有FormControl
。
这就验证了我们之前的猜想,ngModel
指令默认的单向数据绑定,其实就是为我们创建了一个默认的FormControl
用于控制该元素的值。
在不考虑表单验证的前提下,这个基本的新增表单应该就算完成了,在onSubmit
中获取表单的值,然后包装对象调用Service
请求api
。
Update
新增时因为没有初始的数据,所以直接使用默认创建的FormGroup
和FormControl
就行了,但是编辑时是有初始化的数据的,所以我们就需要创建自定义的FormGroup
、FormControl
。
这里是Angular权威指南
中推荐初始化表单的方式,当然也可以去new
。
还是规范,构造和初始化分开,constructor
中使用formBuilder
构造FormGroup
、FormControl
。
myForm: FormGroup; constructor(formBuilder: FormBuilder) { this.myForm = formBuilder.group({ email: '', password: '' }); }
初始化,为FormControl
设置数据,实际开发应该是从后台获取数据然后设置,这里为了演示方便,直接setValue
。
ngOnInit() { this.getOriginData(); } getOriginData(): void { this.myForm.setValue({ email: '[email protected]', password: 'this is password' }); }
数据有了,接下来就是将数据绑定到组件上。将myForm
作为参数传给组件,ngSubmit
不变。
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)"> <div class="form-group"> <label>Email address</label> <input type="email" class="form-control" name="email" placeholder="Email" [formControl]="myForm.controls['email']" /> </div> <div class="form-group"> <label>Password</label> <input type="text" class="form-control" name="password" placeholder="Password" [formControl]="myForm.controls['password']" /> </div> <button type="submit" class="btn btn-default">Submit</button> </form>
绑定成功!
Validate
修改代码,我们删除邮箱与密码的初始化代码,让其为默认的空值。
纵使风云变幻,始终不离其宗。
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)"> <div class="form-group"> <label>Email address</label> <input type="email" class="form-control" name="email" placeholder="Email" [formControl]="myForm.controls['email']" /> </div> <pre>Valid: {{ myForm.controls['email'].valid }}</pre> <pre>Touch: {{ myForm.controls['email'].touched }}</pre> <div class="form-group"> <label>Password</label> <input type="text" class="form-control" name="password" placeholder="Password" [formControl]="myForm.controls['password']" /> </div> <pre>Valid: {{ myForm.controls['password'].valid }}</pre> <pre>Touch: {{ myForm.controls['password'].touched }}</pre> <button type="submit" class="btn btn-default">Submit</button> </form>
我们想对这两个输入框进行验证,直接在初始化时设置验证条件:
constructor(formBuilder: FormBuilder) { this.myForm = formBuilder.group({ email: ['', Validators.compose([ Validators.required, Validators.email ])], password: ['', Validators.required] }); }
实现验证:邮箱的空验证与邮箱格式验证,密码的空验证。
然后符合我们以往的开发规范,用一个ngIf
的p
标签,然后当不合法且触碰过时,显示提示信息。
扩展验证
在以往的AngularJS
项目里,我们只能用已有的验证规则,但是现在更强大了!
其实,我们上面用到的required
、email
等的验证规则都是框架为我们提供的已有的验证函数,翻开ValidatorFn - Angular,有ValidatorFn
接口,实现该接口,即可实现自定义的验证方法!
总结
双向数据绑定的思考:
既然表单都设计得如此强大,我们又何必拘于双向数据绑定,双向数据绑定是我们实现软件功能的一种方式,但不是唯一方式。
如果使用双向数据绑定,那肯定比我上面说的简单许多,但是自从学习了软件测试,渐渐明白了,其实我们在做软件开发时,与我们平常在学校写代码是不一样的。
就像今天吴老师讲的例子:求e
的50
次方。如果在学校,我们可能直接一个for
循环50
次然后一次一次去乘。
但是如果在公司,我们可能就会先算e的平方
,然后再算出e的四次方
,e的八次方
,然后根据几个已有的去组合需要的结果,这会让性能大幅提升。
这是美团点评团队优化小程序的一篇博客:
或许,之前的我们学习了很多知识,学会了不少框架,也写过许多代码,但是我对自己的评价还是程序员。今天,我才明白,何为软件工程师?!
立个小目标:以后写代码时多考虑一点,优秀的软件工程师写出的代码是能够直接上线的。