Powrót do strony głównej

Czyste funkcje w szablonach Angular 21.2

Od wersji 21.2 Angular obsługuje czyste funkcje JS w szablonach do filtrowania, wyszukiwania i aktualizacji sygnałów bez metod komponentu. Analiza przykładów nawigacji, sortowania, przekazywania funkcji przez input. Ograniczenia: zakaz wieloliniowych wyrażeń i pipes wewnątrz funkcji.

Funkcje w szablonach Angular: filtrowanie i sortowanie bez klas
Advertisement 728x90

Czyste funkcje w szablonach Angular 21.2: możliwości i ograniczenia

Od wersji 21.2 Angular umożliwia używanie czystych funkcji JavaScript bezpośrednio w szablonach HTML bez deklarowania metod w klasie komponentu. Upraszcza to przetwarzanie danych, takie jak filtrowanie tablic czy obliczenia na podstawie sygnałów, ale wymaga ścisłego przestrzegania zasad czystości funkcji.

Rozważmy komponent z listą bohaterów, gdzie dane przechowywane są w sygnale heroes typu WritableSignal<Hero[]>:

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[]>([
    // dane bohaterów
  ]);

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

Podstawowe wyświetlanie listy za pomocą @for:

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

Nawigacja po liście bez metod komponentu

Aktualizacja sygnału selectedHeroId odbywa się teraz inline w obsługach kliknięć. Zamiast metod prevHero() i nextHero() używa się update() z funkcją lambda:

<div class="hero-navigation">
  <button (click)="selectedHeroId.update(count => count === 1 ? heroes().length : count - 1)">Wstecz</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)">Dalej</button>
</div>
<div class="hero-info">
  {{ heroes()[selectedHeroId() - 1] | json }}
</div>

To redukuje boilerplate-kod, ale logika nawigacji cyklicznej (powrót do pierwszego elementu) pozostaje w szablonie.

Filtrowanie i wyszukiwanie w szablonach

Filtrowanie bohaterów według dopasowania pierwszych liter imienia i nazwiska:

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>

Wynik: Peter Parker, Stephen Strange, Bruce Banner.

Wyszukiwanie według domeny email:

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

Wynik: Tony.

Google AdInline article slot

Przekazywanie funkcji jako input do komponentów potomnych

Komponent potomny HeroesList przyjmuje funkcję sortującą przez 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()));
}

Użycie w szablonie nadrzędnym:

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

Sortowanie według imienia: Bruce Banner, Natasha Romanoff, Peter Parker, Stephen Strange, Tony Stark.

Obiekty i literały w funkcjach

Dla zwracania obiektów funkcja jest otaczana nawiasami okrągłymi, aby odróżnić od ciała funkcji:

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

Wynik: Super Spider man - [email protected] itd.

Ograniczenia czystych funkcji

  • Funkcje wieloliniowe nie są obsługiwane:

```html

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

```

Kompilator zgłasza błąd.

  • Funkcje zwracające funkcje kompilują się jako tekst:

```html

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

```

  • Pipy wewnątrz funkcji są zabronione, ponieważ to nie jest czysty JS:

```html

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

```

Dopuszczalne jest zastosowanie pipa do wyniku:

```html

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

```

Wynik: TONY.

Co jest ważne

  • Czyste funkcje w szablonach Angular 21.2 nadają się do prostych operacji z sygnałami: filtrowanie, wyszukiwanie, aktualizacja wartości.
  • Obowiązkowe używanie nawiasów okrągłych dla literałów obiektowych i zakaz wyrażeń wieloliniowych.
  • Przekazywanie funkcji przez input-signal pozwala zachować reaktywność w komponentach potomnych.
  • Unikać pipów wewnątrz funkcji; stosować je tylko do wyników.
  • Wydajność: czyste funkcje są przeliczane przy każdej detekcji zmian, preferowane są computed lub pipy dla złożonej logiki.

— Editorial Team

Advertisement 728x90

Czytaj dalej