Angular 中使用双向数据绑定的正确姿势
双向数据绑定是 Angular 框架的核心特性之一,它能让组件类中的数据与模板中的视图元素保持同步,当用户修改视图内容时,组件数据会自动更新;反之,组件数据变化时,视图也会同步刷新。很多刚接触 Angular 的开发者容易混淆双向绑定和单向绑定的使用场景,也可能遇到绑定不生效的问题,本文将详细介绍双向数据绑定的正确使用方法。
双向数据绑定的基本语法
Angular 中双向数据绑定的标准语法是 [(ngModel)],它由属性绑定 [ngModel] 和事件绑定 (ngModelChange) 组合而成,属于"香蕉盒子"语法(方括号包裹圆括号)。
需要注意的是,双向绑定依赖 FormsModule 模块,使用前必须先在项目的根模块(通常是 app.module.ts)中导入该模块,否则会报找不到 ngModel 指令的错误。
// app.module.ts 中导入 FormsModule
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // 导入表单模块
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, FormsModule], // 将 FormsModule 加入 imports 数组
bootstrap: [AppComponent]
})
export class AppModule {}基础使用场景:表单输入绑定
最常见的双向绑定使用场景是绑定表单输入元素,比如文本框、下拉框、单选按钮等。下面以文本输入框为例,展示如何将输入框的值与组件类的属性绑定。
// app.component.ts 组件类
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
// 定义绑定的数据属性
userName: string = '默认用户名';
}<!-- app.component.html 模板 -->
<div class="form-group">
<label for="username">用户名:</label>
<!-- 使用 [(ngModel)] 绑定 userName 属性 -->
<input type="text" id="username" [(ngModel)]="userName" class="form-control">
</div>
<p>当前输入的用户名是:{{ userName }}</p>
<button (click)="changeUserName()" class="btn btn-primary">修改用户名为"新用户"</button>// 组件类中添加修改方法
changeUserName() {
this.userName = '新用户'; // 修改组件数据,视图会自动更新
}运行上述代码后,在输入框中修改内容,下方的段落会实时显示最新输入的值;点击修改按钮,输入框的内容也会同步变成"新用户",这就是双向绑定的效果。
自定义组件实现双向绑定
除了原生表单元素,我们自定义的业务组件也可以支持双向绑定,这时候需要遵循 Angular 的约定:属性名需要和事件名对应,事件名格式为 属性名 + Change。
比如我们创建一个计数器组件,支持外部的双向绑定,组件内部有一个计数值,用户可以点击按钮修改计数值,同时外部也能修改这个计数值。
// counter.component.ts 自定义计数器组件
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-counter',
templateUrl: './counter.component.html'
})
export class CounterComponent {
// 输入属性,用于接收外部传入的计数值
@Input() count: number = 0;
// 输出事件,值变化时触发,事件名必须是 count + Change
@Output() countChange: EventEmitter<number> = new EventEmitter<number>();
// 增加计数值
increment() {
this.count++;
this.countChange.emit(this.count); // 触发事件,通知外部值已变化
}
// 减少计数值
decrement() {
this.count--;
this.countChange.emit(this.count);
}
}<!-- counter.component.html 计数器模板 -->
<div class="counter">
<button (click)="decrement()">-</button>
<span class="count-value">{{ count }}</span>
<button (click)="increment()">+</button>
</div>然后在使用这个自定义组件的父组件中,就可以用双向绑定语法绑定 count 属性了:
<!-- 父组件模板中使用自定义计数器组件 -->
<app-counter [(count)]="parentCount"></app-counter>
<p>父组件中当前计数值:{{ parentCount }}</p>
<button (click)="parentCount = parentCount + 10">父组件直接修改计数值+10</button>// 父组件类中定义 parentCount 属性
export class ParentComponent {
parentCount: number = 0;
}这样无论是点击计数器内部的加减按钮,还是父组件直接修改 parentCount 的值,双方的计数值都会保持同步,实现了自定义组件的双向绑定。
使用注意事项
- 不要在非输入类元素上使用
[(ngModel)],比如 <div>、<p> 这类元素不支持双向绑定,强行使用会报错。 - 如果只需要从组件到视图的单向更新,用属性绑定
[ngModel]即可;如果只需要从视图到组件的更新,用事件绑定(ngModelChange)即可,不需要强求双向绑定。 - 双向绑定会带来一定的性能开销,如果是大型列表或者高频更新的场景,要评估是否有必要使用,避免不必要的性能损耗。
- 自定义组件的双向绑定中,输出事件的名称必须严格遵守
属性名 + Change的格式,否则 Angular 无法识别双向绑定语法。