DEV Community

Cover image for Angular Just Added Arrow Functions to Templates — And I’m Not Sure It’s a Good Idea
Greg Belousov
Greg Belousov

Posted on

Angular Just Added Arrow Functions to Templates — And I’m Not Sure It’s a Good Idea

Angular 21.2 introduced support for arrow functions directly in templates. At first glance, this looks like a long-awaited improvement — less boilerplate, more flexibility. But the more I experimented with it, the more questions I had.

What Changed?

Angular templates can now use pure JavaScript arrow functions inline. This means you no longer need to define simple logic inside your component class — you can write it directly in the template. Let’s look at how this works in practice.

Example Setup

We’ll use a simple component with a list of 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[]>([
    {
      id: 1,
      name: 'Peter',
      lastName: 'Parker',
      nickname: 'Spider man',
      email: 'peter@parker.com'
    },
    {
      id: 2,
      name: 'Tony',
      lastName: 'Stark',
      nickname: 'Iron man',
      email: 'tony@stark.com'
    },
    {
      id: 3,
      name: 'Stephen',
      lastName: 'Strange',
      nickname: 'Doctor Strange',
      email: 'stephen@strange.com',
    },
    {
      id: 4,
      name: 'Natasha',
      lastName: 'Romanoff',
      nickname: 'Black widow',
      email: 'natasha@romanoff.com',
    },
    {
      id: 5,
      name: 'Bruce',
      lastName: 'Banner',
      nickname:'Hulk',
      email: 'bruce@banner.com',
    }
  ]);

  selectedHeroId: WritableSignal<number> = signal<number>(1);
}
Enter fullscreen mode Exit fullscreen mode

Rendering a List

Let’s start simple — rendering all heroes:

<ul>
  @for (hero of heroes(); track $index) {
  <li>
    {{ `${hero.name} ${hero.lastName} - ${hero.email}` }}
  </li>
  }
</ul>
Enter fullscreen mode Exit fullscreen mode

Which gives us:

  • Peter Parker - peter@parker.com
  • Tony Stark - tony@stark.com
  • Stephen Strange - stephen@strange.com
  • Natasha Romanoff - natasha@romanoff.com
  • Bruce Banner - bruce@banner.com

Nothing special here — just standard signal usage.

Updating Signals Without Methods

Now let’s look at more detailed information for each hero.

Using the Previous/Next buttons, we’ll navigate through the heroes list and retrieve data for each hero by its id. The selected id will be stored in a signal called selectedHeroId.

This is where things start to change.

Previously, to update the value of a signal, we would have to define two methods in the component class and call them on button clicks.

<button (click)="prevHero()">Previous</button>
<button (click)="nextHero()">Next</button>
Enter fullscreen mode Exit fullscreen mode
prevHero(): void {
  this.selectedHeroId.update((count: number) => count === 1 ? this.heroes().length : count - 1)
}

nextHero(): void {
  this.selectedHeroId.update((count: number) => count === this.heroes().length ? 1 : count + 1)
}
Enter fullscreen mode Exit fullscreen mode

Now we can do the same directly in the template:

<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 style="hero-info">
  {{ heroes()[selectedHeroId() - 1] | json }}
</div>
Enter fullscreen mode Exit fullscreen mode

This will render:

Hero info

Inline Data Transformations

We can also perform transformations directly in the template.
For example, filtering heroes whose first and last names start with the same letter:

<div>
  {{ heroes().filter(hero => hero.name.charAt(0).toLowerCase() === hero.lastName.charAt(0).toLowerCase()).map(hero => `${hero.name} ${hero.lastName}` ).join(', ') }}
</div>
Enter fullscreen mode Exit fullscreen mode

Output:
Peter Parker, Stephen Strange, Bruce Banner

Finding Data

Let’s try to find a hero by their email domain.

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

Which gives us:
Tony

Passing Functions as Inputs

What about passing an arrow function as an input?
For this example, let’s create another component that renders a list of heroes sorted by name.

import { Component, computed, input, InputSignal, Signal } from '@angular/core';
import { Hero } from '../heroes/heroes.component';

@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()));
}
Enter fullscreen mode Exit fullscreen mode

Now let’s add this component to our main template:

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

This will render:

  • Bruce Banner
  • Natasha Romanoff
  • Peter Parker
  • Stephen Strange
  • Tony Stark

Sorting works as expected.

Object Literals

We can also work with object literals.
To do that, they need to be wrapped in parentheses. Otherwise, the template parser will throw an error.

Template parser error
The reason is that in JavaScript, curly braces after an arrow function are interpreted as a function body, not an object.

❌ Incorrect:

{{ heroes().map(item => { name: 'Super ' + item.nickname, email: item.email }) }}
Enter fullscreen mode Exit fullscreen mode

✅ Correct:

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

Output:

  • Super Spider man - peter@parker.com
  • Super Iron man - tony@stark.com
  • Super Doctor Strange - stephen@strange.com
  • Super Black widow - natasha@romanoff.com
  • Super Hulk - bruce@banner.com

Limitations

There are also things you cannot do in templates.

❌ Multi-line functions are not supported

As shown in the error above, multi-line arrow functions are not allowed:

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

❌ Returning a function

A function should not return another function.
This code will not throw an error, but it also won’t work as expected — Angular will render the compiled function as plain text:

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

Output:
() => { let tmp_0_0; return (tmp_0_0 = ctx.heroes().find((hero) => hero.email.endsWith("stark.com"))) == null ? null : tmp_0_0.name; }

❌ Pipes inside arrow functions

You also cannot use pipes inside arrow functions.
Pipes are part of Angular template syntax, and pure JavaScript does not support them:

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

✅ Correct usage with pipes

However, you can apply a pipe to the result of the expression:

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

Output:
TONY

Final Thoughts

I really like Angular, but this update raises some questions.

On the one hand, using functions in templates can be convenient and may reduce boilerplate in simple cases.

On the other hand:

  • keeping logic out of templates is generally a good practice
  • functions in templates have long been associated with performance concerns
  • pipes are often a better alternative

Personally, I would treat this feature as a tool for small, local transformations — not as a replacement for component logic.

What Do You Think?

Would you use arrow functions in templates in real projects?

What do you see as the main pros and cons?

Top comments (0)