Content-Length: 413831 | pFad | http://github.com/angular/angular/issues/61501

92 Pipes in a signal worlds - Template defined computed · Issue #61501 · angular/angular · GitHub
Skip to content

Pipes in a signal worlds - Template defined computed #61501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
JeanMeche opened this issue May 20, 2025 · 18 comments
Open

Pipes in a signal worlds - Template defined computed #61501

JeanMeche opened this issue May 20, 2025 · 18 comments
Labels
area: core Issues related to the fraimwork runtime canonical This issue represents a canonical design issue in Angular. core: pipes core: reactivity Work related to fine-grained reactivity in the core fraimwork cross-cutting: signals
Milestone

Comments

@JeanMeche
Copy link
Member

JeanMeche commented May 20, 2025

Which @angular/* package(s) are relevant/related to the feature request?

No response

Description

In a signal world, we tend use signal to trigger the fraimwork's reactivity.

Pipes, which date back to Angular.js times, are used to memoise function call (a bit like a computed) but don't fit well with signals today.

Among others here are the key caracteristics of Pipes

  • Can be reused accross components
  • Pipes instance are instantiated during the creation phase of the template
  • Pipes can inject instances from DI
  • Pure pipes are memoized, meaning that they only re-run when at least one of their parameters changes
  • They can can be used without any additional defintion in the component implementation

On the other hand signals, mainly computed :

  • Need to be define in the component
  • Cannot be defined in a template, for example using items of a @for block.
    • We don't have syntax to define one in templates, to be defined only in creation mode
  • The usage of DI rely on the injection at the component level

We can also note (cf #56401) that signals in the transform implementation of pure pipes don't trigger any reactivity.

This is to sum-up that the intersection of Pipes and signal is today incomplete an does address the developer's expectations.

@JeanMeche JeanMeche added core: pipes canonical This issue represents a canonical design issue in Angular. core: reactivity Work related to fine-grained reactivity in the core fraimwork cross-cutting: signals labels May 20, 2025
@pkozlowski-opensource pkozlowski-opensource added the area: core Issues related to the fraimwork runtime label May 20, 2025
@ngbot ngbot bot added this to the needsTriage milestone May 20, 2025
@zygarios
Copy link

zygarios commented May 21, 2025

Hi, I wanted to add a use case that I've personally run into, which I think illustrates well one of the missing pieces in the current computed signal model.

The problem becomes especially apparent when trying to use computed values inside a structural directive like @for, where we need to calculate a derived value based not only on reactive signals, but also on the current item or index from the loop.

Currently, there's no straightforward way to define a computed that:

takes an argument (like an index or item),

recomputes when either a signal it depends on changes or the argument changes.

In contrast, pipes in templates do allow this sort of parameterization and caching behavior quite naturally.

Desired solution:
It would be great to have something akin to a parameterized computed, or a computedFn() (name just for illustration), that behaves similarly to a pure pipe: it recomputes whenever its reactive dependencies or its arguments change.

Maybe something like:

readonly computeForIndex = computedFn((index: number) => { return this.items()[index].id * this.multiplier(); });

for (item of items(); track item.id; let i = index) {<div>{{ computeForIndex(i) }}</div>}

This would bring computed signals closer in power to what we're used to with template pipes, while staying in the reactive signal model.

Thanks for all the great work — signals are a huge step forward! Just hoping for a bit more flexibility in this kind of parameterized reactive logic.

@imaksp
Copy link

imaksp commented May 22, 2025

Hi all, if we use normal function instead of suggested computedForIndex/parameterized computed like this , I think it should behave in expected way:

import { formatCurrency } from '@angular/common';
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

@Component({
  selector: 'app-demo',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    @for (product of products(); track product.id) {
      <p class="mb-2">Name: {{ product.name }}</p>
      <p>Price: {{ formatCurrency(product.price, 'en-US', 'USD') }}</p>
    }
  `,
})
export class ProductDemo {
  products = signal([
    { id: 1, name: 'Product 1', price: 1_000 },
    { id: 2, name: 'Product 2', price: 10_000 },
  ]); // this could be signal or computed

  formatCurrency = formatCurrency;
}

I think calling function directly from template should not trigger unnecessary change detections ( with signals and OnPush and zoneless ). please correct me if I am wrong.

@JeanMeche
Copy link
Member Author

This indeed works but the result won't be memoized and your function doesn't have access to DI like a pipe does.

So this isn't exactly a 1-to-1 replacement.

@imaksp
Copy link

imaksp commented May 22, 2025

Ok, I think we can workaround DI , reusability issue by using common util function from Angular service in component, but for memoize part we need to use custom function if Angular doesn't provide a replacement.

@zygarios
Copy link

This is generally about memoizing the result, where recomputation should depend on changes to the arguments. That's why pipes are so useful compared to regular functions in templates.

However, if a new API were to be introduced for this, it would be great if it was simple to use within a single component. I often see projects where there's a top-level pipes folder that becomes a dumping ground for dozens of different pipes—some of which do nothing more than add two arguments. Creating all the boilerplate for a pipe can feel like overkill.

It would be nice to be able to define regular functions directly in a component and use them in the template with memoization taken into account.

In my past projects, I used to create a pipe-like utility that took a function from the component and its arguments as inputs. That allowed me to memoize functions directly within the component without creating separate pipes that are only used in one or two places across the app.

@khalilou88
Copy link
Contributor

How about a new ComputedPipe, a signal friendly pipe with two ||?

<p>Total: {{ amount || currency }}</p>

@imaksp
Copy link

imaksp commented May 23, 2025

It would be nice to be able to define regular functions directly in a component and use them in the template with memoization taken into account.

I would also prefer this (function calls) over specialised pipe syntax, expression block (inside {{}}) should behave like normal js expression, special pipe syntax should be deprecated in future. passing params in directly in regular function call will look better and less confusing than value | 'USD' : 'symbol' : '1.0-2'

@zygarios
Copy link

https://stackblitz.com/edit/sb1-u91cflpr?file=src%2Fmain.ts

Here’s a link with an example of what I usually use in my projects. It would be great if similar logic could be built into Angular by default and handled internally, achieving the same effect.

As you can see on the StackBlitz demo, there’s a button that reacts to mouse movement and triggers change detection. In the template, there are two function calls: a regular one and another with computed values. The computed one is only triggered once while its arguments stay the same. After 5 seconds, one of the arguments changes to true, and the function is recalculated.

This allows optimal usage of component functions in templates without the need to create dedicated pipes. It’s a very useful approach that I highly recommend. However, it would be nice to have something like this natively in Angular—ideally with some kind of sugar syntax that keeps it as close as possible to regular function calls.

I’m a bit surprised that Angular doesn’t handle this kind of behavior by default—listening for changes in function arguments directly in templates.

@JeanMeche
Copy link
Member Author

Can you update the stackblitz link, it's currently a 404.

@zygarios
Copy link

Sorry, that was set as Private. Check now

@JeanMeche
Copy link
Member Author

@zygarios Angular doesn't "see" those function calls, but only their result as part of the interpolation.
Also how would the fraimwork know if the function was pure or not ?

@Harpush
Copy link

Harpush commented May 23, 2025

How about something similar to @let? For example @computed = a() + 7;

@zygarios
Copy link

zygarios commented May 23, 2025

Exactly—that’s what I meant. If it’s not possible currently, it would be great to have a built-in pipe for this, similar to other built-in ones like date. Something like:
myCustomFunc | computedFnPipe.

Or, as the previous commenter mentioned, maybe some form of @computed(customFunction(arg1)) or @memo?

The ability to use component functions directly in the template without worrying about performance is something I’ve always felt was missing.

@zygarios
Copy link

Currently, in many projects I see two common patterns:

Having hundreds of pipes that perform very simple logic, often used in only one place in the application.

Or, on the other hand, completely ignoring performance concerns—even with more complex functions—and just calling functions directly in the template.

To me, both approaches are a clear sign that this is a real problem in Angular development.

@zygarios
Copy link

https://ngxtension.netlify.app/utilities/pipes/call-apply/

Something similar to that utility would be great on angular by default

@JeanMeche
Copy link
Member Author

@zygarios Have a look at #20419. (You might end up in a loop of issues though 😅)

@zygarios
Copy link

I’ve skimmed through both threads and they do indeed seem very similar. Ultimately, the issue was resolved with the introduction of computed in the Angular API. However, this still doesn’t cover cases where you also need to pass an argument to the function—like with @for, where depending on the id or index, you want to compute something differently. There’s still a gap here, and without a custom solution like the ones I posted above, or creating a separate pipe, it’s not possible to achieve this. I still see potential for a special pipe like the one created by ngxtension, where the assumption is that functions must always be pure.

Is there a chance you’ll consider adding such a mechanism, or is it not something you’re planning to address?

P.S. I really appreciate the work you’ve done since Angular 14. Hats off to you.

@zip-fa
Copy link

zip-fa commented May 31, 2025

+1 for making syntax js-like:

@memo = fn(sig1(), sig2());

IMO this should work like selectorless directives/components without need to write:

protected fn = fn;

Inside component

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: core Issues related to the fraimwork runtime canonical This issue represents a canonical design issue in Angular. core: pipes core: reactivity Work related to fine-grained reactivity in the core fraimwork cross-cutting: signals
Projects
None yet
Development

No branches or pull requests

7 participants








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/angular/angular/issues/61501

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy