Angular 模板驱动表单读取控件值:从插值展示到组件类访问(ngModel 双向绑定实战)
导读
在 Angular 中完成表单搭建后,下一步就是要读取用户输入到表单控件中的值。本篇指南基于 Angular 官方教程 Learn Angular 的“Getting form control values”练习(对应仓库教程步骤 16-form-control-values),完整讲解模板驱动表单下两种取值方式:在模板中用插值语法 {{ }} 展示输入值、在组件类中用 this 引用并编程式读取。学完后你将能够把 [(ngModel)] 绑定的数据呈现在页面上,也能在事件回调(如点击按钮、提交表单)中拿到实时的输入内容,并为过渡到响应式表单打好基础。
前置上下文:模板驱动表单与 ngModel 双向绑定
本练习紧接前一步“Forms Overview”(见 15-forms/README.md)。在使用模板驱动表单时,需要通过以下两步让输入框与组件属性建立数据连接:
- 从
@angular/forms导入FormsModule并加入组件的imports数组; - 在
<input>上使用[(ngModel)]="favoriteFramework"语法完成双向绑定。
[(ngModel)] 这种“中括号包小括号”的写法俗称 banana-in-a-box(盒子里的香蕉),它本质上等价于 属性绑定(property binding)+ 事件绑定(event binding) 的组合,即:[ngModel] 把组件属性的值推送到输入框,(ngModelChange) 把输入框的新值回写到组件属性。正是因为这一层双向同步,用户每次键入内容,favoriteFramework 都会实时更新,后续所有取值方式才能拿到最新值。
方法一:在模板中展示输入控件的值(插值语法 {{ }})
读取表单控件值最简单的方式,就是像读取组件中任意一个 class 属性那样,在模板中使用插值语法 {{ }} 将值渲染到页面上。以下为带高亮的原始示例(第 5 行为 Framework: {{ favoriteFramework }} 插值渲染):
@Component({
selector: 'app-user',
template: `
...
<p>Framework: {{ favoriteFramework }}</p>
<label for="framework">
Favorite Framework:
<input id="framework" type="text" [(ngModel)]="favoriteFramework" />
</label>
`,
})
export class User {
favoriteFramework = '';
}
这里的要点是:
favoriteFramework只是组件类上的一个普通字符串属性,其默认值为''(空字符串);- 插值语法对表单绑定的属性同样适用,无需任何特殊 API;
- Angular 的变更检测会自动保持
{{ favoriteFramework }}与输入框内容同步,因此用户输入时,页面上的Framework:文本会随之实时刷新。
与本步骤配套的完整起始代码
仓库中该练习的起始工程位于 16-form-control-values/src/app/user.ts。可以看到初始化代码里已经包含了用户名展示、输入框双向绑定以及一个空的 showFramework() 方法,这正是本步骤要完成的改造对象:
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
@Component({
selector: 'app-user',
template: `
<p>Username: {{ username }}</p>
<p>Framework:</p>
<label for="framework">
Favorite Framework:
<input id="framework" type="text" [(ngModel)]="favoriteFramework" />
</label>
<button (click)="showFramework()">Show Framework</button>
`,
imports: [FormsModule],
})
export class User {
favoriteFramework = '';
username = 'youngTech';
showFramework() {}
}
对比可见:app.ts 作为根组件(selector 为 app-root)仅负责渲染 <app-user />,所有练习逻辑都集中在 user.ts 中,这也解释了为何该练习的配置文件把默认打开文件设为 src/app/user.ts。
方法二:在组件类中访问输入控件的值(this 语法)
当需要在组件类内部引用输入框的值时(例如提交表单、拼接请求参数、做本地校验或弹窗提示),只需用 this 直接访问绑定的类属性即可。因为 [(ngModel)] 已经把输入框的值实时同步到了该属性上,组件类里读到的永远是当前最新的输入内容。
在模板上通过事件绑定把按钮点击连接到组件方法,然后在方法内用 this.favoriteFramework 读取值并弹出提示:
...
@Component({
selector: 'app-user',
template: `
...
<button (click)="showFramework()">Show Framework</button>
`,
...
})
export class User {
favoriteFramework = '';
...
showFramework() {
alert(this.favoriteFramework);
}
}
本步骤的标准答案
仓库提供了练习完成的参考答案,位于 16-form-control-values/answer/src/app/user.ts。它同时完成了“模板展示”和“类中读取”两步:第 8 行把 Framework: {{ favoriteFramework }} 渲染到页面,第 21-23 行在 showFramework() 中通过 alert(this.favoriteFramework) 弹出当前输入值:
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
@Component({
selector: 'app-user',
template: `
<p>Username: {{ username }}</p>
<p>Framework: {{ favoriteFramework }}</p>
<label for="framework">
Favorite Framework:
<input id="framework" type="text" [(ngModel)]="favoriteFramework" />
</label>
<button (click)="showFramework()">Show Framework</button>
`,
imports: [FormsModule],
})
export class User {
favoriteFramework = '';
username = 'youngTech';
showFramework() {
alert(this.favoriteFramework);
}
}
运行验证的行为是:在输入框中键入内容 → 页面上的 Framework: 文本实时更新 → 点击 “Show Framework” 按钮,弹出窗口显示与输入完全一致的字符串。
原理纵深:从 @angular/forms 源码看双向绑定的取值链路
为什么“在类里用 this 就能取到值”?这层机制由 @angular/forms 包中的 NgModel 指令提供。查阅仓库源码 packages/forms/src/directives/ng_model.ts 可以还原完整调用链:
- 第 172 行
export class NgModel extends NgControl implements OnChanges, OnDestroy,即NgModel继承了表单控件抽象NgControl,同时具备向模板公开值同步的能力; - 第 237 行
@Output('ngModelChange') update = new EventEmitter();,即ngModelChange事件输出,正是[(ngModel)]事件绑定那一半的真正实现(banana-in-a-box 拆开后对应(ngModelChange)); - 第 364 行
override viewToModelUpdate(newValue: any): void,该回调把视图(输入框)产生的新值通过事件输出回写到组件属性,从而让this.favoriteFramework始终持有最新输入; - 第 227-230 行的
@Input('ngModelOptions') options还表明,值/有效性的更新时机updateOn默认是'change',可配置为'change' | 'blur' | 'submit'。
因此,从数据流角度看本练习的两种取值方式殊途同归:插值表达式 {{ favoriteFramework }} 与 this.favoriteFramework 读取的是同一份被 NgModel 双向绑定实时维护的组件属性,只是前者在模板层渲染、后者在类中编程式访问。
练习设计:编辑器内实时反馈
本练习在 Learn Angular 教程中以“编辑器(editor)”类型的任务呈现(见 config.json),默认只打开 src/app/user.ts。这种设计刻意让学习者把注意力集中在:仅靠修改模板插值、补全一个事件处理函数,就能同时验证“显示值”和“读取值”两条路径,直观感受模板驱动表单的声明式特性——不必手动监听事件或编写同步代码。
下一步:从模板驱动表单走向响应式表单
本练习完成了模板驱动表单从“绑定”到“取值”的闭环。教程紧接着引入 Angular 管理表单的另一种方式——响应式表单(见 17-reactive-forms/README.md)。在响应式表单中,取值逻辑从“组件属性 + 双向绑定”转变为以 FormControl、FormGroup 为核心的编程式模型:模板中通过 [formGroup] 与 formControlName 关联控件,读取值时使用 this.profileForm.value.name 这类结构,提交时借助 (ngSubmit) 事件配合 handleSubmit() 方法。可以对比看出:模板驱动表单的值天然与组件属性合一,取值极简;而响应式表单把控件状态集中收拢进 FormGroup,在大规模、动态化的表单场景下更便于程序化管理与校验。
小结
- 展示输入值:模板中用插值
{{ favoriteFramework }},与渲染普通 class 属性完全一致; - 读取输入值:组件类中用
this.favoriteFramework,前提是输入框已通过[(ngModel)]建立双向绑定; - 底层支撑:
NgModel指令(ng_model.ts)通过ngModelChange输出与viewToModelUpdate回调,保证类属性与视图持续同步,使两条取值路径都能拿到实时数据; - 完整参考代码见 16-form-control-values/answer/src/app/user.ts,本步骤教程原文见 16-form-control-values/README.md,前一课建立双向绑定的过程见 15-forms/README.md。
掌握这步之后,你就可以接着学习如何将表单值用于提交、校验,以及如何用响应式表单在组件类中全面接管控件状态。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00