Angular模板语法与插值

本章目标:掌握Angular模板的核心语法,包括插值、数据绑定、指令、管道和模板表达式,能够编写灵活高效的Angular模板。

什么是Angular模板?

Angular模板是用HTML编写的一段代码,它告诉Angular如何在页面上渲染组件。模板包含普通的HTML元素,以及Angular特有的模板语法:

模板语法流程图
插值表达式 {{ }} 属性绑定 [] 事件绑定 () 双向绑定 [()]

1. 插值表达式 (Interpolation)

使用双花括号{{ }}将组件中的数据显示在模板中,这是最简单的数据绑定形式。

组件代码
import { Component } from '@angular/core';

@Component({
  template: `
    <h1>{{ title }}</h1>
    <p>欢迎, {{ userName }}!</p>
    <p>今天是: {{ currentDate | date:'yyyy-MM-dd' }}</p>
    <p>计算面积: {{ width * height }}</p>
    <p>{{ getGreeting() }}</p>
  `
})
export class AppComponent {
  title = 'Angular模板教程';
  userName = '张三';
  currentDate = new Date();
  width = 10;
  height = 5;

  getGreeting(): string {
    return `你好,${this.userName}!`;
  }
}
渲染结果

Angular模板教程

欢迎, 张三!

今天是: 2024-01-15

计算面积: 50

你好,张三!

插值表达式的特点
  • 可以显示字符串、数字、布尔值等基本类型
  • 可以调用组件的方法
  • 支持简单的算术运算
  • 可以使用管道格式化数据
  • 表达式应该是快速执行的,避免复杂计算

2. 属性绑定 (Property Binding)

使用方括号[]将组件属性绑定到DOM元素的属性或组件的输入属性。

绑定到HTML属性
<!-- 绑定到标准HTML属性 -->
<img [src]="imageUrl" [alt]="imageAlt">

<!-- 绑定到class属性 -->
<div [class.active]="isActive"></div>

<!-- 绑定到style属性 -->
<button [style.color]="isValid ? 'green' : 'red'">
  提交
</button>

<!-- 绑定到disabled属性 -->
<button [disabled]="isLoading">
  {{ isLoading ? '加载中...' : '提交' }}
</button>
绑定到组件输入属性
<!-- 父组件模板 -->
<app-user-card
  [user]="currentUser"
  [showDetails]="true"
  [maxWidth]="300">
</app-user-card>

<!-- 子组件定义 -->
@Component({...})
export class UserCardComponent {
  @Input() user: any;
  @Input() showDetails: boolean;
  @Input() maxWidth: number;
}
绑定到表单控件
<!-- 绑定到表单控件的value -->
<input [value]="userName">

<!-- 绑定到自定义属性 -->
<div [attr.data-id]="userId"></div>

<!-- 绑定到ARIA属性 -->
<button [attr.aria-label]="buttonLabel">
  操作
</button>
属性绑定 vs 插值: <img src="{{ imageUrl }}"><img [src]="imageUrl"> 是等价的,但属性绑定更明确且能绑定非字符串值。

3. 事件绑定 (Event Binding)

使用圆括号()监听DOM事件并执行组件中的方法。

事件绑定示例
常用DOM事件绑定
<!-- 点击事件 -->
<button (click)="onClick()">点击我</button>

<!-- 输入事件 -->
<input (input)="onInput($event)">

<!-- 键盘事件 -->
<input (keyup)="onKeyUp($event)"
       (keydown)="onKeyDown($event)"
       (keyup.enter)="onEnter()">

<!-- 鼠标事件 -->
<div (mouseenter)="onMouseEnter()"
     (mouseleave)="onMouseLeave()">
  悬停区域
</div>

<!-- 表单事件 -->
<form (submit)="onSubmit($event)">
  <button type="submit">提交</button>
</form>

<!-- 自定义事件 -->
<app-child (userSelected)="onUserSelected($event)">
</app-child>
组件事件处理方法
export class EventDemoComponent {
  // 处理点击事件
  onClick(): void {
    console.log('按钮被点击了');
    alert('点击事件触发!');
  }

  // 处理输入事件,获取输入值
  onInput(event: Event): void {
    const input = event.target as HTMLInputElement;
    console.log('输入内容:', input.value);
  }

  // 处理键盘事件
  onKeyUp(event: KeyboardEvent): void {
    console.log('按键释放:', event.key);
    if (event.key === 'Enter') {
      console.log('Enter键被按下');
    }
  }

  // 处理自定义事件
  onUserSelected(user: any): void {
    console.log('用户被选中:', user);
  }

  // 带参数的事件处理
  onButtonClick(buttonId: string): void {
    console.log(`按钮 ${buttonId} 被点击`);
  }
}

4. 双向绑定 (Two-Way Binding)

使用[()](称为"香蕉盒"语法)实现视图和组件的双向数据同步。

双向绑定实时演示
输入内容会自动更新下方显示
当前状态

用户名: Angular开发者

选择的框架: Angular

开发经验:

双向绑定的实现方式
使用ngModel(需要FormsModule)
// 1. 在app.module.ts中导入FormsModule
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [FormsModule],
  ...
})

// 2. 在组件模板中使用
<input [(ngModel)]="userName"
       placeholder="请输入用户名">

<select [(ngModel)]="selectedFramework">
  <option value="Angular">Angular</option>
  <option value="React">React</option>
</select>

<textarea [(ngModel)]="description"></textarea>

// 3. 组件中的属性会自动更新
export class UserFormComponent {
  userName = '';
  selectedFramework = 'Angular';
  description = '';
}
自定义双向绑定
// 1. 创建支持双向绑定的组件
@Component({
  selector: 'app-toggle',
  template: `
    <button (click)="toggle()"
            [class.active]="value">
      {{ value ? 'ON' : 'OFF' }}
    </button>
  `
})
export class ToggleComponent {
  @Input() value: boolean = false;
  @Output() valueChange = new EventEmitter<boolean>();

  toggle(): void {
    this.value = !this.value;
    this.valueChange.emit(this.value);
  }
}

// 2. 在父组件中使用
<app-toggle [(value)]="isActive"></app-toggle>

// 这相当于:
<app-toggle [value]="isActive"
            (valueChange)="isActive = $event">
</app-toggle>

5. 模板表达式与模板语句

理解模板表达式和模板语句的区别和限制。

模板表达式

在插值{{ }}和属性绑定[]中使用的表达式

  • 应该:快速执行,无副作用
  • 不应该:赋值、new、自增/自减
  • 可以:调用方法、算术运算、三元表达式
// 有效的模板表达式
{{ title }}
{{ count + 1 }}
{{ isValid ? '是' : '否' }}
{{ getFullName() }}
{{ items.length }}
模板语句

在事件绑定()中使用的表达式

  • 可以:赋值、调用方法、有副作用
  • 不应该:返回值
  • 典型用法:处理用户交互
// 有效的模板语句
(click)="save()"
(click)="count = count + 1"
(input)="user.name = $event.target.value"
(click)="delete(item); refresh()"
(keyup.enter)="onEnter()"
模板上下文

模板表达式可以访问组件实例的成员,也可以访问模板输入变量、模板引用变量和指令的上下文变量。

// 组件中的属性
export class ContextComponent {
  items = ['Angular', 'React', 'Vue'];
  selectedItem = '';

  selectItem(item: string): void {
    this.selectedItem = item;
  }
}
<!-- 模板中使用 -->
<ul>
  <li *ngFor="let item of items"
      (click)="selectItem(item)">
    {{ item }}
  </li>
</ul>

<!-- 这里可以访问 -->
<!-- 1. 组件属性:items, selectedItem -->
<!-- 2. 模板输入变量:item(在*ngFor中) -->
<!-- 3. 方法:selectItem -->

6. 模板引用变量

使用#创建对DOM元素或组件的引用,可以在模板中直接访问。

引用DOM元素
<!-- 引用input元素 -->
<input #nameInput
       type="text"
       placeholder="输入姓名">

<button (click)="focusInput(nameInput)">
  聚焦输入框
</button>

<!-- 组件中访问 -->
export class RefComponent {
  focusInput(inputEl: HTMLInputElement): void {
    inputEl.focus();
    inputEl.select();
  }
}
引用Angular组件
<!-- 引用子组件 -->
<app-user-form #userForm></app-user-form>

<button (click)="submitForm(userForm)">
  提交表单
</button>

<!-- 在模板中直接调用组件方法 -->
<p>表单是否有效: {{ userForm.isValid }}</p>

<!-- 通过ViewChild在组件中访问 -->
export class ParentComponent {
  @ViewChild('userForm') userForm!: UserFormComponent;

  submitForm(): void {
    if (this.userForm.isValid) {
      this.userForm.submit();
    }
  }
}
引用指令
<!-- 引用ngModel指令 -->
<input #name="ngModel"
       ngModel
       required
       name="userName">

<div *ngIf="name.invalid && name.touched">
  <div *ngIf="name.errors?.['required']">
    用户名是必填项
  </div>
</div>

7. 管道 (Pipes)

管道用于在模板中转换数据,使用|操作符。

内置管道示例

原始数据:

  • 字符串: "angular framework"
  • 数字: 1234.5678
  • 百分比: 0.75
  • 货币: 199.99
  • 日期: new Date()
  • 数组: [1, 2, 3, 4, 5]

管道处理后:

  • {{ "angular framework" | uppercase }}
  • {{ 1234.5678 | number:'1.2-2' }}
  • {{ 0.75 | percent }}
  • {{ 199.99 | currency:'CNY':'symbol' }}
  • {{ new Date() | date:'yyyy-MM-dd HH:mm' }}
  • {{ [1,2,3,4,5] | slice:1:3 }}
常用内置管道
管道 用途 示例
date 日期格式化 {{ date | date:'yyyy-MM-dd' }}
uppercase 转换为大写 {{ text | uppercase }}
lowercase 转换为小写 {{ text | lowercase }}
currency 货币格式化 {{ price | currency:'USD' }}
percent 百分比格式化 {{ ratio | percent }}
json JSON格式化(调试用) {{ object | json }}
async 订阅Observable/Promise {{ data$ | async }}
链式管道和自定义管道
<!-- 链式管道 -->
<p>{{ birthday | date:'fullDate' | uppercase }}</p>

<!-- 带参数的管道 -->
<p>{{ price | currency:'EUR':'symbol':'1.2-2' }}</p>

<!-- 自定义管道 -->
<p>{{ text | truncate:20 }}</p>

// 自定义管道实现
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit: number = 50): string {
    if (value.length <= limit) return value;
    return value.substring(0, limit) + '...';
  }
}

8. 安全导航操作符 (?.)

使用?.避免在访问可能为null/undefined的对象属性时出错。

不使用安全操作符
// 组件代码
export class UserComponent {
  user: any = null; // 初始为null

  // 模板中访问
  // {{ user.name }}  // 会抛出错误
}
当user为null时,访问user.name会抛出"Cannot read property 'name' of null"错误
使用安全操作符
<!-- 安全访问嵌套属性 -->
<p>用户名: {{ user?.name }}</p>
<p>城市: {{ user?.address?.city }}</p>
<p>邮编: {{ user?.address?.zipCode }}</p>

<!-- 与管道结合使用 -->
<p>{{ user?.birthday | date:'yyyy-MM-dd' }}</p>

<!-- 在属性绑定中使用 -->
<img [src]="user?.avatarUrl"
     [alt]="user?.name">

<!-- 在事件绑定中使用 -->
<button (click)="user?.save()">
  保存
</button>
使用?.操作符,当user为null时,表达式会返回undefined而不会抛出错误

模板语法最佳实践

推荐做法
  • 使用属性绑定而不是字符串插值来绑定非字符串属性
  • 在模板中保持表达式简单,复杂的逻辑移到组件中
  • 使用安全导航操作符处理可能为null的值
  • 为管道提供适当的参数以控制输出格式
  • 使用模板引用变量来访问DOM元素或子组件
  • 在事件处理中传递$event对象以获取事件详情
应避免的做法
  • 避免在模板表达式中执行复杂计算或副作用
  • 不要忘记在双向绑定中导入FormsModule
  • 避免在事件绑定中直接修改组件状态(通过方法更好)
  • 不要过度使用管道链,考虑在组件中预处理数据
  • 避免在模板中写很长的表达式,可读性差
  • 不要忘记处理异步数据的加载状态

本章总结

通过本章学习,你应该掌握了Angular模板语法的核心概念:

  • 插值表达式{{ }}用于显示数据
  • 属性绑定[]用于设置DOM属性和组件输入
  • 事件绑定()用于处理用户交互
  • 双向绑定[()]用于表单输入的双向同步
  • 模板引用变量#用于引用元素或组件
  • 管道|用于数据转换和格式化
  • 安全导航操作符?.用于避免空值错误