返回首页

Angular 模板中的纯函数 21.2

自 21.2 版本起 Angular 支持在模板中使用纯 JS 函数进行过滤、搜索和更新 signals,无需组件方法。导航、排序、通过 input 传递函数示例详解。限制:禁止函数内部多行表达式和管道。

Angular 模板中的函数:无类过滤和排序
Advertisement 728x90

Angular 21.2 模板中的纯函数:能力与限制

从版本 21.2 开始,Angular 允许直接在 HTML 模板中使用纯 JavaScript 函数,而无需在组件类中声明方法。这简化了数据处理,如数组过滤或基于信号的计算,但需要严格遵守纯函数规则。

考虑一个包含英雄列表的组件,数据存储在类型为 WritableSignal<Hero[]>heroes 信号中:

import { Component, signal, WritableSignal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeroesList } from '../heroes-list/heroes-list';

export interface Hero {
  id: number;
  name: string;
  lastName: string;
  nickname: string;
  email: string;
}

@Component({
  selector: 'app-heroes',
  imports: [HeroesList, CommonModule],
  templateUrl: './heroes.component.html',
  styleUrl: './heroes.component.scss',
  standalone: true
})
export class HeroesComponent {
  heroes: WritableSignal<Hero[]> = signal<Hero[]>([
    // 英雄数据
  ]);

  selectedHeroId: WritableSignal<number> = signal<number>(1);
}

通过 @for 输出基本列表:

Google AdInline article slot
<ul>
  @for (hero of heroes(); track $index) {
    <li>{{ `${hero.name} ${hero.lastName} - ${hero.email}` }}</li>
  }
</ul>

无需组件方法导航列表

现在,更新 selectedHeroId 信号在点击处理程序中内联完成。使用 update() 和 lambda 函数替代 prevHero()nextHero() 方法:

<div class="hero-navigation">
  <button (click)="selectedHeroId.update(count => count === 1 ? heroes().length : count - 1)">上一个</button>
  <div class="hero-name">
    {{ heroes()[selectedHeroId() - 1].name + ' ' + heroes()[selectedHeroId() - 1].lastName }}
  </div>
  <button (click)="selectedHeroId.update(count => count === heroes().length ? 1 : count + 1)">下一个</button>
</div>
<div class="hero-info">
  {{ heroes()[selectedHeroId() - 1] | json }}
</div>

这减少了样板代码,但循环导航逻辑(返回第一个元素)仍保留在模板中。

模板中的过滤和搜索

通过匹配名字和姓氏的首字母过滤英雄:

Google AdInline article slot
<div>
  {{ heroes().filter(hero => hero.name.charAt(0).toLowerCase() === hero.lastName.charAt(0).toLowerCase())
    .map(hero => `${hero.name} ${hero.lastName}`).join(', ') }}
</div>

结果:Peter Parker, Stephen Strange, Bruce Banner。

按邮箱域名搜索:

<div>
  {{ heroes().find(hero => hero.email.endsWith('stark.com'))?.name }}
</div>

输出:Tony。

Google AdInline article slot

将函数作为输入传递给子组件

子组件 HeroesList 通过 input 接受排序函数:

@Component({
  selector: 'app-heroes-list',
  template: `
    <ul>
      @for (hero of sortedHeroes(); track $index;) {
        <li>{{ hero.name + ' ' + hero.lastName }}</li>
      }
    </ul>
  `,
  standalone: true
})
export class HeroesList {
  heroes: InputSignal<Hero[]> = input.required<Hero[]>();
  sortFn: InputSignal<(a: Hero, b: Hero) => number> = input.required<(a: Hero, b: Hero) => number>();

  sortedHeroes: Signal<Hero[]> = computed(() => [...this.heroes()].sort(this.sortFn()));
}

在父模板中使用:

<app-heroes-list [heroes]="heroes()" [sortFn]="(a, b) => a.name.localeCompare(b.name)"></app-heroes-list>

按名字排序:Bruce Banner, Natasha Romanoff, Peter Parker, Stephen Strange, Tony Stark。

函数中的对象和字面量

要返回对象,函数需用括号包裹以区分函数体:

<ul>
  @for (hero of heroes().map(item => ({ name: 'Super ' + item.nickname, email: item.email })); track $index) {
    <li>{{ `${hero.name} - ${hero.email}` }}</li>
  }
</ul>

结果:Super Spider man - [email protected] 等。

纯函数的限制

  • 不支持多行函数

```html

{{ heroes().map(item => { return { name: 'Super ' + item.nickname, email: item.email } }) }}

```

编译器会抛出错误。

  • 返回函数的函数会被编译为文本:

```html

{{ () => heroes().find(hero => hero.email.endsWith('stark.com'))?.name }}

```

  • 函数内部禁止使用管道,因为这不是纯 JS:

```html

{{ heroes().find(hero => hero.email.endsWith('StaRk.com' | lowercase))?.name }}

```

对结果应用管道是允许的:

```html

{{ heroes().find(hero => hero.email.endsWith('stark.com'))?.name | uppercase }}

```

结果:TONY。

关键要点

  • Angular 21.2 模板中的纯函数适用于信号的简单操作:过滤、搜索、更新值。
  • 必须使用括号处理对象字面量,并禁止多行表达式。
  • 通过输入信号传递函数可在子组件中保持响应性。
  • 避免在函数内部使用管道;仅对结果应用管道。
  • 性能:纯函数在每次变更检测时重新计算;对于复杂逻辑,优先使用计算属性或管道。

— Editorial Team

Advertisement 728x90

继续阅读