模板与数据绑定 #
一、模板概述 #
Angular模板使用HTML语法,并扩展了特殊语法来操作数据绑定和事件处理。
二、插值表达式 #
2.1 基本语法 #
html
<p>{{ message }}</p>
<p>{{ 1 + 1 }}</p>
<p>{{ 'Hello ' + name }}</p>
<p>{{ user.name }}</p>
<p>{{ getFullName() }}</p>
2.2 插值表达式特点 #
typescript
@Component({
template: `
<!-- 表达式计算 -->
<p>1 + 1 = {{ 1 + 1 }}</p>
<!-- 字符串拼接 -->
<p>{{ 'Hello, ' + name + '!' }}</p>
<!-- 调用方法 -->
<p>{{ formatPrice(price) }}</p>
<!-- 三元表达式 -->
<p>{{ isActive ? '激活' : '未激活' }}</p>
<!-- 不支持 -->
<!-- <p>{{ new Date() }}</p> -->
<!-- <p>{{ const a = 1 }}</p> -->
<!-- <p>{{ ++count }}</p> -->
`
})
export class ExampleComponent {
name = 'Angular';
price = 100;
isActive = true;
formatPrice(price: number): string {
return `¥${price.toFixed(2)}`;
}
}
三、属性绑定 #
3.1 基本语法 #
html
<!-- 属性绑定 -->
<img [src]="imageUrl" />
<a [href]="linkUrl">链接</a>
<button [disabled]="isDisabled">按钮</button>
<!-- 等价于 -->
<img src="{{ imageUrl }}" />
3.2 属性绑定类型 #
typescript
@Component({
template: `
<!-- 元素属性 -->
<input [value]="inputValue" />
<img [src]="imageUrl" [alt]="imageAlt" />
<!-- 组件属性 -->
<app-user [user]="currentUser"></app-user>
<!-- 指令属性 -->
<div [ngClass]="classes"></div>
<div [ngStyle]="styles"></div>
`
})
export class ExampleComponent {
inputValue = 'Hello';
imageUrl = '/assets/logo.png';
imageAlt = 'Logo';
currentUser = { name: 'John' };
classes = { active: true, disabled: false };
styles = { color: 'red', fontSize: '16px' };
}
3.3 属性绑定与插值的区别 #
html
<!-- 属性绑定:推荐用于非字符串属性 -->
<button [disabled]="isDisabled">按钮</button>
<!-- 插值:推荐用于字符串属性 -->
<img src="{{ imageUrl }}" />
<!-- 属性绑定更安全 -->
<img [src]="userInput" />
四、类绑定 #
4.1 单类绑定 #
html
<div [class.active]="isActive">内容</div>
<div [class.hidden]="!isVisible">隐藏内容</div>
4.2 多类绑定 #
html
<div [class]="{'active': isActive, 'disabled': isDisabled}">内容</div>
4.3 ngClass指令 #
typescript
@Component({
template: `
<div [ngClass]="classes">内容</div>
<div [ngClass]="['class1', 'class2']">内容</div>
<div [ngClass]="getClassNames()">内容</div>
`
})
export class ExampleComponent {
classes = {
active: true,
disabled: false,
'text-bold': true
};
getClassNames(): string[] {
return ['class1', 'class2'];
}
}
五、样式绑定 #
5.1 单样式绑定 #
html
<div [style.color]="'red'">红色文字</div>
<div [style.background-color]="'blue'">蓝色背景</div>
<div [style.fontSize.px]="16">16px字体</div>
<div [style.width.%]="100">100%宽度</div>
5.2 ngStyle指令 #
typescript
@Component({
template: `
<div [ngStyle]="styles">内容</div>
<div [ngStyle]="getDynamicStyles()">动态样式</div>
`
})
export class ExampleComponent {
styles = {
color: 'red',
'font-size': '16px',
'background-color': 'lightblue'
};
getDynamicStyles() {
return {
color: this.isActive ? 'green' : 'gray',
'font-weight': this.isBold ? 'bold' : 'normal'
};
}
}
六、事件绑定 #
6.1 基本语法 #
html
<button (click)="onClick()">点击</button>
<input (input)="onInput($event)" />
<form (submit)="onSubmit($event)"></form>
6.2 事件对象 #
typescript
@Component({
template: `
<input (input)="onInput($event)" />
<button (click)="onClick($event)">点击</button>
<div (mousemove)="onMouseMove($event)"></div>
`
})
export class ExampleComponent {
onInput(event: Event) {
const value = (event.target as HTMLInputElement).value;
console.log(value);
}
onClick(event: MouseEvent) {
console.log('点击位置:', event.clientX, event.clientY);
}
onMouseMove(event: MouseEvent) {
console.log('鼠标位置:', event.clientX, event.clientY);
}
}
6.3 事件传递参数 #
html
<button (click)="onSelect(user)">选择用户</button>
<button (click)="onDelete(user.id)">删除</button>
<button (click)="onEdit(user, $event)">编辑</button>
typescript
onSelect(user: User) {
console.log('选中:', user);
}
onDelete(id: number) {
console.log('删除ID:', id);
}
onEdit(user: User, event: Event) {
event.stopPropagation();
console.log('编辑:', user);
}
6.4 键盘事件 #
html
<input (keyup)="onKeyUp($event)" />
<input (keyup.enter)="onEnter()" />
<input (keyup.escape)="onEscape()" />
<input (keydown.control.a)="onCtrlA()" />
typescript
onKeyUp(event: KeyboardEvent) {
console.log('按键:', event.key);
}
onEnter() {
console.log('按下回车');
}
6.5 自定义事件 #
typescript
@Component({
selector: 'app-counter',
template: `
<button (click)="increment()">+1</button>
<span>{{ count }}</span>
<button (click)="decrement()">-1</button>
`
})
export class CounterComponent {
count = 0;
@Output() countChange = new EventEmitter<number>();
increment() {
this.count++;
this.countChange.emit(this.count);
}
decrement() {
this.count--;
this.countChange.emit(this.count);
}
}
七、双向绑定 #
7.1 基本语法 #
html
<input [(ngModel)]="name" />
<p>Hello, {{ name }}!</p>
7.2 双向绑定原理 #
html
<!-- [(ngModel)] 等价于 -->
<input
[ngModel]="name"
(ngModelChange)="name = $event"
/>
7.3 自定义双向绑定 #
typescript
@Component({
selector: 'app-custom-input',
template: `
<input
[value]="value"
(input)="onInput($event)"
/>
`
})
export class CustomInputComponent {
@Input() value: string = '';
@Output() valueChange = new EventEmitter<string>();
onInput(event: Event) {
const value = (event.target as HTMLInputElement).value;
this.valueChange.emit(value);
}
}
使用:
html
<app-custom-input [(value)]="myValue"></app-custom-input>
7.4 表单控件双向绑定 #
typescript
import { FormsModule } from '@angular/forms';
@Component({
standalone: true,
imports: [FormsModule],
template: `
<input [(ngModel)]="user.name" placeholder="姓名" />
<input [(ngModel)]="user.email" placeholder="邮箱" />
<select [(ngModel)]="user.gender">
<option value="male">男</option>
<option value="female">女</option>
</select>
<textarea [(ngModel)]="user.bio" placeholder="简介"></textarea>
`
})
export class UserFormComponent {
user = {
name: '',
email: '',
gender: 'male',
bio: ''
};
}
八、模板引用变量 #
8.1 基本语法 #
html
<input #nameInput />
<button (click)="logName(nameInput.value)">提交</button>
<p>你输入的是: {{ nameInput.value }}</p>
8.2 常见用法 #
typescript
@Component({
template: `
<!-- 引用表单 -->
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<input name="name" ngModel />
<button type="submit" [disabled]="userForm.invalid">提交</button>
</form>
<!-- 引用组件 -->
<app-child #childComponent></app-child>
<button (click)="childComponent.reset()">重置</button>
<!-- 引用元素 -->
<input #email type="email" />
<button (click)="focusEmail(email)">聚焦</button>
`
})
export class ExampleComponent {
onSubmit(form: NgForm) {
console.log(form.value);
}
focusEmail(input: HTMLInputElement) {
input.focus();
}
}
九、模板表达式 #
9.1 表达式上下文 #
typescript
@Component({
template: `
<!-- 组件实例属性 -->
<p>{{ message }}</p>
<!-- 模板变量 -->
<div *ngFor="let item of items">
<p>{{ item.name }}</p>
</div>
<!-- 模板引用变量 -->
<input #input />
<p>{{ input.value }}</p>
`
})
export class ExampleComponent {
message = 'Hello';
items = [{ name: 'Item 1' }, { name: 'Item 2' }];
}
9.2 表达式指南 #
html
<!-- 好的做法 -->
<p>{{ title }}</p>
<p>{{ user.name }}</p>
<p>{{ getPrice() }}</p>
<p>{{ items.length }}</p>
<!-- 不好的做法 -->
<p>{{ new Date() }}</p> <!-- 不应该创建对象 -->
<p>{{ deleteAll() }}</p> <!-- 不应该有副作用 -->
<p>{{ window.location }}</p> <!-- 不应该访问全局变量 -->
十、管道 #
10.1 内置管道 #
html
<!-- 日期管道 -->
<p>{{ birthday | date }}</p>
<p>{{ birthday | date:'yyyy-MM-dd' }}</p>
<p>{{ birthday | date:'fullDate' }}</p>
<!-- 大小写管道 -->
<p>{{ name | uppercase }}</p>
<p>{{ name | lowercase }}</p>
<p>{{ name | titlecase }}</p>
<!-- 数字管道 -->
<p>{{ price | number }}</p>
<p>{{ price | number:'1.2-2' }}</p>
<p>{{ price | currency:'CNY':'symbol':'1.2-2' }}</p>
<!-- 百分比管道 -->
<p>{{ progress | percent:'1.0-0' }}</p>
<!-- JSON管道 -->
<pre>{{ user | json }}</pre>
<!-- Slice管道 -->
<p>{{ message | slice:0:10 }}</p>
10.2 链式管道 #
html
<p>{{ message | uppercase | slice:0:20 }}</p>
<p>{{ price | currency:'CNY' | uppercase }}</p>
十一、安全导航操作符 #
11.1 基本用法 #
html
<!-- 防止null/undefined错误 -->
<p>{{ user?.name }}</p>
<p>{{ user?.address?.city }}</p>
<p>{{ items?.length }}</p>
11.2 与管道结合 #
html
<p>{{ user?.birthday | date }}</p>
<p>{{ user?.name | uppercase }}</p>
十二、总结 #
| 绑定类型 | 语法 | 方向 |
|---|---|---|
| 插值 | {{ expression }} |
组件→模板 |
| 属性绑定 | [property]="expression" |
组件→模板 |
| 事件绑定 | (event)="statement" |
模板→组件 |
| 双向绑定 | [(property)]="expression" |
双向 |
下一步:组件通信
最后更新:2026-03-26