# [(ngModel)] 用表单处理用户输入是许多常见应用的基础功能。 应用通过表单来让用户登录、修改个人档案、输入敏感信息以及执行各种数据输入任务。 Angular 提供了两种不同的方法来通过表单处理用户输入:响应式表单和模板驱动表单。 在此我们学习在生产项目使用频率较**低**的模板驱动表单。 > [info] 除特殊说明外,本节代码操作均位于`src/app/add`文件夹。 ## C层初始化 首先,我们在C层中初始化一个教师,打开`app.component.ts`: ```typescript import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-add', templateUrl: './add.component.html', styleUrls: ['./add.component.css'] }) export class AddComponent implements OnInit { constructor() { } ngOnInit(): void { } } ``` 如下初始化教师teacher: ```typescript export class AddComponent implements OnInit { teacher = { name: '', username: '', email: '', sex: true }; constructor() { } ngOnInit(): void { } } ``` 接下来,定义一个当用户点击『保存』按钮时执行的方法,通常来讲,如果C层中的某个方法的作用是被V层的某个动作(我们把用户点击『保存』按钮称为一个动作)触发,则该方法名将以`on`打头,比如此时我们起名为`onSubmit()`,作用为在控制台中打印当前教师的值: ```typescript ngOnInit(): void { } onSubmit(): void { console.log(this.teacher); } } ``` ## V层绑定方法 在angular中,当表单被提交时,将触发`form`元素的 `ngSubmit`方法,我们如下将`ngSubmit`绑定到C层的`onSubmit()`方法上: ```html <form class="container-sm" (ngSubmit)="onSubmit()"> ``` 细心的你可能也注意到了,聪明的webstrom编辑器在此提示了一个错误: ![image-20210224211933920](https://img.kancloud.cn/a3/e9/a3e9f8dd262dd4a43ca84942882ac883_1094x138.png) 这是由于angular虽然内置了对`ngSubmit`方法的支持,但却并未默认开启。为了达到高度的可定制化,angular把一些内置的功能分别放到了内置的不同模块中。而若想使这些功能生效,则需要在当前模块中引入(imports)这些带有功能的模块。在angular中,对`ngSubmit`方法的支持模块名为:`FormsModule`。 我们打开当前组件所在模块`AppModule`对应的文件`src/app/app.module.ts`,并将`FormsModule`添加到`imports`中: ```typescript import {HttpClientModule} from '@angular/common/http'; import {AddComponent} from './add/add.component'; +import {FormsModule} from '@angular/forms'; imports: [ BrowserModule, AppRoutingModule, - HttpClientModule + HttpClientModule, + FormsModule ], ``` > [success] 本节开始,我们将使用国际通用的文件比较方法来展示文件的变化情况。`-`代表原文件本行删除,`+`代表在原文件此行上新增。 此时webstrom对应的错误消失: ![image-20210224212719979](https://img.kancloud.cn/e8/20/e82041e5895dff36541c52b222103643_889x84.png) 如果接下来,我们还需要将`FormsModule`添加到当前的单元测试文件中,以使其在当前动态模块下生效: ``` 👉 import {FormsModule} from '@angular/forms'; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [AddComponent], imports: [FormsModule] ✚ }) .compileComponents(); }); ``` - 👉 在后续教程中,我们将逐步省略**非**首次引入代码。 此时当我们在V层中点击『保存』按钮时,C层中的`onSubmit()`将被执行,控制台打印信息如下: ![image-20210224213859283](https://img.kancloud.cn/8f/64/8f6495e127d611b8f1d2b039c949d74c_702x193.png) ## V层绑定数据 模板驱动表单提供了一种非常简单的数据绑定方式 ---- `[(ngModel)]="xxx"`,比如我们可以使用如下代码将C层teacher中的属性快速的绑定到V层中: ```html <div class="col-sm-10"> - <input type="text" class="form-control" id="name"> + <input type="text" class="form-control" ➊name="name" [(ngModel)]="teacher.name" id="name"> </div> </div> <div class="mb-3 row"> <label for="username" class="col-sm-2 col-form-label">用户名</label> <div class="col-sm-10"> - <input type="text" class="form-control" id="username"> + <input type="text" class="form-control" ➊name="username" [(ngModel)]="teacher.username" id="username"> </div> </div> <div class="mb-3 row"> <label for="email" class="col-sm-2 col-form-label">Email</label> <div class="col-sm-10"> - <input type="text" class="form-control" id="email"> + <input type="text" class="form-control" ➊name="email" [(ngModel)]="teacher.email" id="email"> </div> ``` **注意:** ➊ 我们在为input添加`[(ngModel)]`的同时,还必须为其增加`name`属性。 此时我们为表单加入相关测试信息后点击『保存』按钮,将在控制台中打印如下信息,这充分的说明用户在V层中输入的数据成功的传入了C层。 ![image-20210224220547140](https://img.kancloud.cn/0b/27/0b279531c87f3a7a5ab09a9bc9dd4db5_806x391.png) 由于我们在性别中需要接收一个`boolean`类型的值,所以性别所使用的`radio`的处理的稍微麻烦一些: ```html <div class="form-check form-check-inline"> - <input class="form-check-input" type="radio" name="inlineRadioOptions" id="sex-male" value="option1"> + <input class="form-check-input" type="radio" id="sex-male" + name="sex" ➊ + [(ngModel)]="teacher.sex" ➋ + [value]="true" ➌> <label class="form-check-label" for="sex-male">男</label> </div> <div class="form-check form-check-inline"> - <input class="form-check-input" type="radio" name="inlineRadioOptions" id="sex-female" value="option2"> + <input class="form-check-input" type="radio" id="sex-female" + name="sex" ➊ + [(ngModel)]="teacher.sex" ➋ + [value]="false" ➌> <label class="form-check-label" for="sex-female">女</label> </div> ``` - ➊ 使用`[(ngModel)]`时必须设置`name`值,且两个radio的值必须一致 - ➋ 两个radio绑定同一值`teacher.sex` - ➌ 此处使用的是`[value]`="true"而非`value="true"` 选择性别后,点击保存按钮在控制台中成功打印出了`sex`的值: ![image-20210224221618944](https://img.kancloud.cn/c8/56/c856faca05c6f7330036408681339798_875x255.png) ## 本节资源 | 名称 | 地址 | 备注 | | -------------- | ------------------------------------------------------------ | ----------------------- | | 建立响应式表单 | [https://angular.cn/guide/forms-overview#setup-in-reactive-forms](https://angular.cn/guide/forms-overview#setup-in-reactive-forms) | | | 保存表单数据 | [https://angular.cn/guide/reactive-forms#grouping-form-controls](https://angular.cn/guide/reactive-forms#grouping-form-controls) | **保存表单数据** 一小节 | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step2.3.2.zip](https://github.com/mengyunzhi/angular11-guild/archive/step2.3.2.zip) | |