本章目标:深入掌握Angular四种数据绑定方式,理解绑定原理,熟练使用@Input和@Output实现组件通信,掌握最佳实践。
四种数据绑定方式
{{ }}
插值绑定
组件 → 视图
[ ]
属性绑定
组件 → 视图
( )
事件绑定
视图 → 组件
[( )]
双向绑定
视图 ↔ 组件
数据绑定概述
数据绑定是Angular的核心机制,它自动同步组件类(数据)和组件模板(UI)之间的数据。
组件类 (TypeScript)
组件模板 (HTML)
| 绑定类型 | 语法 | 方向 | 用途 | 示例 |
|---|---|---|---|---|
| 插值绑定 | {{ expression }} |
组件 → 视图 | 显示组件数据到模板 | {{ userName }} |
| 属性绑定 | [property]="expression" |
组件 → 视图 | 设置元素/组件属性 | [src]="imageUrl" |
| 事件绑定 | (event)="handler()" |
视图 → 组件 | 响应用户交互事件 | (click)="onClick()" |
| 双向绑定 | [(ngModel)]="property" |
视图 ↔ 组件 | 表单输入的双向同步 | [(ngModel)]="userName" |
1. 插值绑定 (Interpolation)
使用双花括号{{ }}将组件中的数据插入到模板中,是最简单的单向数据绑定。
组件类
export class InterpolationComponent {
// 基本类型
title = 'Angular数据绑定';
count = 42;
isActive = true;
price = 99.99;
// 对象
user = {
name: '张三',
age: 28,
email: 'zhangsan@example.com'
};
// 数组
skills = ['TypeScript', 'Angular', 'RxJS'];
// 方法
getGreeting(): string {
return `你好,${this.user.name}!`;
}
// 计算属性
get discountPrice(): number {
return this.price * 0.8;
}
// 当前时间
currentDate = new Date();
}
模板代码
<h1>{{ title }}</h1>
<!-- 显示基本类型 -->
<p>计数: {{ count }}</p>
<p>状态: {{ isActive ? '活跃' : '不活跃' }}</p>
<p>原价: {{ price }} 元</p>
<p>折后价: {{ discountPrice }} 元</p>
<!-- 显示对象属性 -->
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
<p>{{ getGreeting() }}</p>
<!-- 使用管道格式化 -->
<p>当前时间: {{ currentDate | date:'yyyy-MM-dd HH:mm:ss' }}</p>
<!-- 数组长度 -->
<p>技能数量: {{ skills.length }}</p>
<!-- 复杂表达式(应避免) -->
<p>总价: {{ count * price }} 元</p>
<!-- 安全导航操作符 -->
<p>用户城市: {{ user?.address?.city || '未设置' }}</p>
实时演示
2. 属性绑定 (Property Binding)
使用方括号[ ]将组件属性绑定到HTML元素属性、组件输入属性或指令属性。
属性绑定流程
1
定义组件属性
在组件类中创建要绑定的数据
2
创建绑定表达式
在模板中使用[property]="expression"语法
3
Angular处理绑定
Angular计算表达式并将结果设置到目标属性
4
更新DOM
当组件属性变化时,Angular自动更新DOM
绑定到HTML属性
<!-- 绑定到标准HTML属性 -->
<img [src]="imageUrl"
[alt]="imageAlt"
[width]="imageWidth">
<!-- 绑定到class属性 -->
<div [class.active]="isActive"
[class.disabled]="isDisabled">
内容区域
</div>
<!-- 绑定多个class -->
<div [class]="getClassList()"></div>
<!-- 绑定到style属性 -->
<button [style.color]="isValid ? 'green' : 'red'"
[style.font-size.px]="fontSize">
提交
</button>
<!-- 绑定多个样式 -->
<div [style]="getStyles()"></div>
<!-- 绑定到disabled属性 -->
<button [disabled]="isLoading">
{{ isLoading ? '处理中...' : '提交' }}
</button>
<!-- 绑定到自定义数据属性 -->
<div [attr.data-user-id]="userId"></div>
绑定到组件属性
// 父组件
@Component({
template: `
<app-user-card
[user]="currentUser"
[showAvatar]="true"
[maxWidth]="300"
[theme]="'dark'">
</app-user-card>
`
})
export class ParentComponent {
currentUser = {
name: '李四',
role: '管理员'
};
}
// 子组件
@Component({...})
export class UserCardComponent {
@Input() user: any;
@Input() showAvatar: boolean = false;
@Input() maxWidth?: number;
@Input() theme: 'light' | 'dark' = 'light';
// 监听输入变化
ngOnChanges(changes: SimpleChanges): void {
if (changes['user']) {
console.log('用户数据已更新', changes['user'].currentValue);
}
}
}
属性绑定 vs 字符串插值:
<img src="{{ imageUrl }}">- 插值方式<img [src]="imageUrl">- 属性绑定方式- 两者效果相同,但属性绑定可以绑定非字符串值,更明确清晰
3. 事件绑定 (Event Binding)
使用圆括号( )监听DOM事件并执行组件中的方法,实现视图到组件的数据流。
事件绑定交互演示
常见DOM事件
<!-- 点击事件 -->
<button (click)="onButtonClick($event)">
点击我
</button>
<!-- 输入事件 -->
<input (input)="onInputChange($event.target.value)"
placeholder="输入内容">
<!-- 键盘事件 -->
<input (keydown)="onKeyDown($event)"
(keyup)="onKeyUp($event)"
(keyup.enter)="onEnter()"
placeholder="按Enter键提交">
<!-- 鼠标事件 -->
<div (mouseenter)="onMouseEnter()"
(mouseleave)="onMouseLeave()"
(mousemove)="onMouseMove($event)">
鼠标悬停区域
</div>
<!-- 表单事件 -->
<form (submit)="onFormSubmit($event)"
(reset)="onFormReset()">
<button type="submit">提交</button>
</form>
<!-- 聚焦/失焦事件 -->
<input (focus)="onFocus()"
(blur)="onBlur()">
<!-- 滚动事件 -->
<div (scroll)="onScroll($event)"
style="height: 200px; overflow: auto;">
<p>可滚动内容...</p>
</div>
组件事件处理
export class EventBindingComponent {
clickCount = 0;
inputValue = '';
mousePosition = { x: 0, y: 0 };
keyLog: string[] = [];
// 处理点击事件
onButtonClick(event: MouseEvent): void {
this.clickCount++;
console.log('按钮被点击', event);
event.stopPropagation(); // 阻止事件冒泡
}
// 处理输入变化
onInputChange(value: string): void {
this.inputValue = value;
console.log('输入内容:', value);
}
// 处理键盘事件
onKeyDown(event: KeyboardEvent): void {
console.log('按键按下:', event.key);
this.keyLog.push(`按下: ${event.key}`);
}
// 处理鼠标移动
onMouseMove(event: MouseEvent): void {
this.mousePosition = {
x: event.clientX,
y: event.clientY
};
}
// 带参数的事件处理
onItemClick(itemId: number): void {
console.log('项目被点击:', itemId);
}
// 防止默认行为
onLinkClick(event: Event): void {
event.preventDefault();
console.log('链接点击被阻止');
}
// 使用 $event 对象
onCustomEvent(event: CustomEvent): void {
console.log('自定义事件:', event.detail);
}
}
4. 双向绑定 (Two-Way Binding)
使用[(ngModel)]("香蕉盒"语法)实现视图和组件之间的双向数据同步。
双向绑定原理
[(ngModel)]="property" 是以下两种绑定的语法糖:
[ngModel]="property"
(ngModelChange)="property = $event"
使用ngModel
// 1. 导入FormsModule
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [FormsModule],
// ...
})
// 2. 组件中使用双向绑定
export class TwoWayBindingComponent {
// 表单数据
user = {
name: '',
email: '',
age: 25,
gender: 'male',
interests: [],
subscribe: true,
bio: ''
};
// 选项数据
genderOptions = [
{ value: 'male', label: '男' },
{ value: 'female', label: '女' }
];
interestOptions = [
{ value: 'coding', label: '编程' },
{ value: 'music', label: '音乐' },
{ value: 'sports', label: '运动' }
];
// 数据变化时触发
onDataChange(): void {
console.log('数据已更新:', this.user);
}
}
表单模板
<!-- 文本输入框 -->
<input [(ngModel)]="user.name"
placeholder="请输入姓名">
<!-- 邮箱输入框 -->
<input type="email"
[(ngModel)]="user.email"
placeholder="请输入邮箱">
<!-- 数字输入框 -->
<input type="number"
[(ngModel)]="user.age"
min="0" max="100">
<!-- 下拉选择框 -->
<select [(ngModel)]="user.gender">
<option *ngFor="let option of genderOptions"
[value]="option.value">
{{ option.label }}
</option>
</select>
<!-- 多选框组 -->
<div *ngFor="let option of interestOptions">
<input type="checkbox"
[value]="option.value"
[checked]="user.interests.includes(option.value)"
(change)="toggleInterest(option.value)">
{{ option.label }}
</div>
<!-- 单选框 -->
<input type="radio"
[(ngModel)]="user.gender"
value="male"> 男
<input type="radio"
[(ngModel)]="user.gender"
value="female"> 女
<!-- 复选框 -->
<input type="checkbox"
[(ngModel)]="user.subscribe"> 订阅通知
<!-- 文本域 -->
<textarea [(ngModel)]="user.bio"
rows="4"></textarea>
双向绑定实时演示
18
28
60
当前数据
用户名:
Angular开发者
年龄:
28
技能等级:
中级
更新次数:
0
组件间数据通信
通过@Input()和@Output()实现父子组件之间的数据通信。
父组件向子组件传值 (@Input)
// 子组件:接收数据
@Component({
selector: 'app-user-info',
template: `
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>年龄: {{ user.age }}</p>
<p>邮箱: {{ user.email }}</p>
<p *ngIf="showDetails">详情: {{ user.details }}</p>
</div>
`
})
export class UserInfoComponent {
@Input() user: any; // 必填输入
@Input() showDetails = false; // 可选输入,有默认值
// 监听输入变化
ngOnChanges(changes: SimpleChanges): void {
console.log('输入属性变化:', changes);
}
}
// 父组件:传递数据
@Component({
template: `
<app-user-info
[user]="currentUser"
[showDetails]="true">
</app-user-info>
`
})
export class ParentComponent {
currentUser = {
name: '王五',
age: 35,
email: 'wangwu@example.com',
details: '高级工程师'
};
}
子组件向父组件传值 (@Output)
// 子组件:发送事件
@Component({
selector: 'app-product-item',
template: `
<div class="product">
<h4>{{ product.name }}</h4>
<p>价格: {{ product.price }}元</p>
<button (click)="addToCart()">
加入购物车
</button>
<button (click)="removeFromCart()">
移除
</button>
</div>
`
})
export class ProductItemComponent {
@Input() product: any;
@Output() added = new EventEmitter<any>();
@Output() removed = new EventEmitter<any>();
@Output() quantityChange = new EventEmitter<number>();
addToCart(): void {
this.added.emit(this.product);
}
removeFromCart(): void {
this.removed.emit(this.product);
}
updateQuantity(quantity: number): void {
this.quantityChange.emit(quantity);
}
}
// 父组件:监听事件
@Component({
template: `
<app-product-item
[product]="selectedProduct"
(added)="onProductAdded($event)"
(removed)="onProductRemoved($event)">
</app-product-item>
<p>购物车数量: {{ cartCount }}</p>
`
})
export class ParentComponent {
selectedProduct = { name: 'Angular教程', price: 99 };
cartCount = 0;
onProductAdded(product: any): void {
console.log('产品已添加:', product);
this.cartCount++;
}
onProductRemoved(product: any): void {
console.log('产品已移除:', product);
this.cartCount--;
}
}
数据绑定最佳实践
推荐做法
- 对非字符串属性使用属性绑定而不是插值
- 在模板中保持表达式简单,复杂逻辑移到组件方法中
- 使用安全导航操作符
?.避免空值错误 - 为
@Input()属性提供合适的默认值 - 在事件处理中传递
$event获取事件详情 - 使用
ngOnChanges监听输入属性变化 - 为双向绑定提供清晰的初始值
应避免的做法
- 避免在模板表达式中执行复杂计算或有副作用的操作
- 不要忘记为双向绑定导入
FormsModule - 不要在事件绑定中直接大量修改组件状态
- 避免过多的嵌套属性绑定,考虑使用中间变量
- 不要忘记在
ngOnDestroy中清理订阅 - 避免在Angular表达式使用
new、自增/自减 - 不要混合使用多种绑定方式处理同一数据
本章总结
通过本章学习,你应该掌握了:
- 插值绑定:使用
{{ }}显示组件数据到视图 - 属性绑定:使用
[ ]将组件属性绑定到元素属性 - 事件绑定:使用
( )监听DOM事件并执行组件方法 - 双向绑定:使用
[( )]实现表单输入的双向同步 - 组件通信:使用
@Input()和@Output()进行父子组件通信 - 最佳实践:编写高效、可维护的数据绑定代码