5

问题描述

Web开发中,表单一直是一个重要的话题。

AngularJS中,我们可以使用双向数据绑定很简单地完成表单的开发,但是会带来严重的性能问题,而Angular对于表单的设计,让我们的表单在保持性能的同时更优雅。

实例

我们以一个最简单的登录表单为例来学习Angular中的表单:

clipboard.png

思想

clipboard.png

这就是Angular的表单思想,一个FormGroup管理整个表单,同时FormControl管理表单内的各个元素。

Create

导入表单模块:

clipboard.png

基础的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事件。

clipboard.png

<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
</form>

这里用到了导出的ngForm,并为其取名为myForm,所以传给onSubmitmyForm其实就是导出的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);
}

clipboard.png

提交表单,打印,可以查看myForm中的许多属性,同时它的value属性就是表单内容。

点开NgForm,其实除了表单的值外,还有dirtyerrorinvalid等属性方便我们对表单进行验证。

clipboard.png

点开controls属性,我们可以看到该表单中的FormControl

clipboard.png

<input type="email" class="form-control" name="email" placeholder="Email" />
<input type="password" class="form-control" name="password" placeholder="Password" />

我们尝试将input中原来添加的ngModel指令删除,再打印。可以看到该表单中没有FormControl

clipboard.png

这就验证了我们之前的猜想,ngModel指令默认的单向数据绑定,其实就是为我们创建了一个默认的FormControl用于控制该元素的值。

在不考虑表单验证的前提下,这个基本的新增表单应该就算完成了,在onSubmit中获取表单的值,然后包装对象调用Service请求api

Update

新增时因为没有初始的数据,所以直接使用默认创建的FormGroupFormControl就行了,但是编辑时是有初始化的数据的,所以我们就需要创建自定义的FormGroupFormControl

这里是Angular权威指南中推荐初始化表单的方式,当然也可以去new

还是规范,构造和初始化分开,constructor中使用formBuilder构造FormGroupFormControl

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>

绑定成功!

clipboard.png

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>

clipboard.png

我们想对这两个输入框进行验证,直接在初始化时设置验证条件:

constructor(formBuilder: FormBuilder) {
    this.myForm = formBuilder.group({
        email: ['', Validators.compose([
            Validators.required,
            Validators.email
        ])],
        password: ['', Validators.required]
    });
}

实现验证:邮箱的空验证与邮箱格式验证,密码的空验证。

clipboard.png

然后符合我们以往的开发规范,用一个ngIfp标签,然后当不合法且触碰过时,显示提示信息。

扩展验证

在以往的AngularJS项目里,我们只能用已有的验证规则,但是现在更强大了!

clipboard.png

clipboard.png

其实,我们上面用到的requiredemail等的验证规则都是框架为我们提供的已有的验证函数,翻开ValidatorFn - Angular,有ValidatorFn接口,实现该接口,即可实现自定义的验证方法!

clipboard.png

总结

双向数据绑定的思考:

既然表单都设计得如此强大,我们又何必拘于双向数据绑定,双向数据绑定是我们实现软件功能的一种方式,但不是唯一方式。

如果使用双向数据绑定,那肯定比我上面说的简单许多,但是自从学习了软件测试,渐渐明白了,其实我们在做软件开发时,与我们平常在学校写代码是不一样的。

就像今天吴老师讲的例子:求e50次方。如果在学校,我们可能直接一个for循环50次然后一次一次去乘。

但是如果在公司,我们可能就会先算e的平方,然后再算出e的四次方e的八次方,然后根据几个已有的去组合需要的结果,这会让性能大幅提升。

这是美团点评团队优化小程序的一篇博客:

clipboard.png

或许,之前的我们学习了很多知识,学会了不少框架,也写过许多代码,但是我对自己的评价还是程序员。今天,我才明白,何为软件工程师?!

立个小目标:以后写代码时多考虑一点,优秀的软件工程师写出的代码是能够直接上线的。

张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。