在同时启动后台、前台的基础上,我们进行对接测试,打开地址:[http://localhost:4200/edit/1](http://localhost:4200/edit/1),得到如下结果并且未在控制台中发生错误,说明对接成功。 ![](https://img.kancloud.cn/67/6a/676a90e13a3eded2f44ef739c4e3b9a5_599x300.png) # 表单绑定 在新增教师的小节中,我们学习了数据双向绑定,成功的实现了在C层中获取V层表单中的最新数据。本节我们在新增教师代码的基础上,稍做调整。 TeacherEditComponent ``` export class TeacherEditComponent implements OnInit { ... /** * 提交表单 */ onSubmit(): void { console.log('submit'); } } ``` teacher-edit.component.html ``` <pre>{{teacher | json}}</pre> <form id="teacherEditForm①" (ngSubmit)="onSubmit()"> ② <div> <label for="name">姓名:</label> <input type="text" id="name" name="name" [(ngModel)]="teacher.name"/> ➊ </div> <div> <label for="username">用户名:</label> <input type="text" id="username" name="username" [(ngModel)]="teacher.username"> ➊ </div> <div> <label for="email">邮箱:</label> <input type="email" id="email" name="email" [(ngModel)]="teacher.email"> ➊ </div> <div> <label>性别:</label> <label> <input type="radio" name="sex" value="true" [(ngModel)]="teacher.sex"> 男</label> ➊ <label> <input type="radio" name="sex" value="false" [(ngModel)]="teacher.sex"> 女</label> ➊ </div> <div> <button>提交</button> </div> </form> ``` * ➊ 由以前直接绑定`name`等变量,改为绑定`teacher`对象的`name`属性。 ## 测试 浏览[http://localhost:4200/edit/1](http://localhost:4200/edit/1)并测试: ![](https://img.kancloud.cn/98/33/9833a44aecf426656e69c0f4ded553a5_449x361.png) 虽然大体得到了我们想要的结果,查控制台中发生了一个错误: ``` TeacherEditComponent.html:5 ERROR TypeError: Cannot read property 'name' of undefined at Object.eval [as updateDirectives] (TeacherEditComponent.html:5) ``` 它在说在TeacherEditComponent.html的第5行上发生了一个类型错误,不能够在`undefined`上读取`'name'`属性。该错误产生的原因是这样: TeacherEditComponent ``` export class TeacherEditComponent implements OnInit { public teacher: any; ➊ ... ngOnInit(): void { ➋ const id = this.route.snapshot.paramMap.get('id'); const url = 'http://localhost:8080/Teacher/' + id; this.httpClient.get(url) .subscribe((data) => { this.teacher = data; ➍ }, () => { console.log(`请求 ${url} 时发生错误`); }); } ➌ ... } ``` 组件的执行顺序如下: * ➊ 定义了变量teacher,声明类型为任意类型`any`,并使用`undefined`对其进行初始化。 * ➋ V层渲染前,执行`ngOnInit()`方法。 * ➌ 该方法执行完毕后,渲染V层。 * ☀ V层第一次渲染,由于`teacher`的值为`undefined`所以控制台报错。 * ☀ V层第二次渲染,由于`teacher`的值为`undefined`所以控制台报错。 * ➍ 用获取到的值对`teacher`赋值。 * ☀ V层第三次渲染,由于`teacher`当前的值为对象,且存在`name`属性,所以成功的进行绑定。 > 至于为什么会在执行➍以前渲染两次V层并不属于本教程的讨论范围,如果你真的对它有极浓厚的兴趣。可以在教程学习完成后,尝试阅读:[生生命周期勾子](https://www.angular.cn/guide/lifecycle-hooks#lifecycle-hooks),或许你能找到自己想要的答案。 发生错语的原因找到了,那么解决该错误的方法就很简单了。 TeacherEditComponent ``` public teacher: any; ✘ public teacher: any = {}; ✚ ➊ ``` > 在教程中我们将使用`✘`来表示由原文件中删除该行代码,用`✚`来表示在文件中增加代码。所以上述代码的实现含义为:将`public teacher: any;`修改为:`public teacher: any = {};` * ➊ 定义变量的同时使用`{}`来赋初值,规避了变量定义后默认为`undefined`的问题。 > `{}`和`undefined`是有本质区别的。`{}` :存在该对象,但该对象是张白纸;`undefined`不存在该对象。比如现在我计划养一条白毛狗,并把狗的名字起名为`大白`,此时`大白`就等于是`undefined`;过后的几天我真的买回来了一条刚出生的小狗,而它就是我的`大白`,此时`大白`就等于是`{}`。 再次测试控制台打印的异常便如期消失了。 ## true与"true" 但最终的问题仍然没有修正,通过查看V层的打印数据我们发现后台是成功了回传了教师的性别sex字段的。但在V层中却没有成功的选重性别,这是为什么呢? ![](https://img.kancloud.cn/12/c0/12c00c223eaf014c4b9d17de8c905c43_301x277.png) 我们随便的点击一个性别观察发生了什么: ![](https://img.kancloud.cn/91/13/911332a6e1e8b0de63755d74ee84e543_220x236.png) 后台返回的是`true`,而点击V层的性别后,设置的值是`"true"`。问题的关键就在于此:`true`的类型为`boolean`,在数据存储时占用1个bit;而`"true"`是个`string`,在数据存储时,最少占用4*8 = 32个bit。`true`与`"true"`仅仅是在显示时长的像了一些而已。那么解决这个问题的方法应该有两个:1 让`spring`返回性别的时候,返回字符串代替boolean。2 angular V层中进行性别绑定时,使用boolean代替字符串。 我们在开发系统的时候,特别是前后台进行对接的时候经常会遇到此类的问题,这时候就应该讨论下事情的本质应该是个什么样子了。比如性别字段,它的本质就是`boolean`非真既假,那么此时后台的数据返回就没有错,处理该问题需要来处理前台。在`angualr`中的模板驱动表单中,可以使用`[value]`指令来绑定表单中的`boolean`值。 teacher-edit.component.html ``` <label> <input type="radio" name="sex" value="true" [(ngModel)]="teacher.sex"> 男</label> ✘ <label> <input type="radio" name="sex" value="false" [(ngModel)]="teacher.sex"> 女</label> ✘ <label> <input type="radio" name="sex" [value]="true" [(ngModel)]="teacher.sex"> 男</label> ✚ <label> <input type="radio" name="sex" [value]="false" [(ngModel)]="teacher.sex"> 女</label> ✚ ``` 保存文件后浏览器自动刷新后成功的绑定了教师的姓别。 ***** 甲方技术认为:我需要快速的来解决我所遇到的当前问题,只要能解决问题那么就是好的,什么数据库的第一范式、第二范式都是狗屁,对什么面向接口、面向对象全部嗤之以鼻。 我们认为:事情应该是个什么样子就得是个什么样子,规范化才能规避一些意想不到的问题,才能够最大限度的减少文档,减少大家的沟通成本。这是一种本质的开发理念的冲突,坚持规范可能并不是最快的解决方案,但一定是最优的解决方案。 ***** # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | 使用ngModel进行双向数据绑定 | [https://www.angular.cn/guide/forms#two-way-data-binding-with-ngmodel](https://www.angular.cn/guide/forms#two-way-data-binding-with-ngmodel) | 5 | | 生命周期勾子 | [https://www.angular.cn/guide/lifecycle-hooks](https://www.angular.cn/guide/lifecycle-hooks) | 15 | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.4.3](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.4.3) | - |