Back to Home

Pure functions in Angular templates 21.2

Since version 21.2 Angular supports pure JS functions in templates for filtering, searching and updating signals without component methods. Breakdown of navigation, sorting, passing functions via input examples. Limitations: ban on multiline expressions and pipes inside functions.

Functions in Angular templates: filtering and sorting without classes
Advertisement 728x90

Pure Functions in Angular 21.2 Templates: Capabilities and Limitations

Starting with version 21.2, Angular allows the use of pure JavaScript functions directly in HTML templates without declaring methods in the component class. This simplifies data handling like array filtering or computations based on signals, but requires strict adherence to pure function rules.

Consider a component with a list of heroes, where data is stored in a heroes signal of type 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[]>([
    // hero data
  ]);

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

Basic list output via @for:

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

Navigating the List Without Component Methods

Updating the selectedHeroId signal is now done inline in click handlers. Instead of prevHero() and nextHero() methods, update() with a lambda function is used:

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

This reduces boilerplate code, but the cyclic navigation logic (returning to the first element) remains in the template.

Filtering and Searching in Templates

Filtering heroes by matching the first letters of first and last names:

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>

Result: Peter Parker, Stephen Strange, Bruce Banner.

Searching by email domain:

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

Output: Tony.

Google AdInline article slot

Passing Functions as Input to Child Components

The child component HeroesList accepts a sorting function via 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()));
}

Usage in the parent template:

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

Sorted by name: Bruce Banner, Natasha Romanoff, Peter Parker, Stephen Strange, Tony Stark.

Objects and Literals in Functions

To return objects, the function is wrapped in parentheses to distinguish it from the function body:

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

Result: Super Spider man - [email protected], etc.

Limitations of Pure Functions

  • Multiline functions are not supported:

```html

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

```

The compiler throws an error.

  • Functions returning functions are compiled as text:

```html

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

```

  • Pipes inside functions are prohibited, as this is not pure JS:

```html

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

```

Applying a pipe to the result is allowed:

```html

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

```

Result: TONY.

Key Takeaways

  • Pure functions in Angular 21.2 templates are suitable for simple operations with signals: filtering, searching, updating values.
  • Mandatory use of parentheses for object literals and prohibition of multiline expressions.
  • Passing functions via input-signal maintains reactivity in child components.
  • Avoid pipes inside functions; apply them only to results.
  • Performance: pure functions are recalculated on every change detection; prefer computed or pipes for complex logic.

— Editorial Team

Advertisement 728x90

Read Next