模板与数据绑定 #

一、模板概述 #

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