问题描述
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: 'zhangxishuo1998@gmail.com',
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的八次方
,然后根据几个已有的去组合需要的结果,这会让性能大幅提升。
这是美团点评团队优化小程序的一篇博客:
或许,之前的我们学习了很多知识,学会了不少框架,也写过许多代码,但是我对自己的评价还是程序员。今天,我才明白,何为软件工程师?!
立个小目标:以后写代码时多考虑一点,优秀的软件工程师写出的代码是能够直接上线的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。