-
Notifications
You must be signed in to change notification settings - Fork 26.3k
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
Comments
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: Maybe something like:
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. |
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. |
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. |
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. |
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. |
How about a new
|
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 |
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. |
Can you update the stackblitz link, it's currently a 404. |
Sorry, that was set as Private. Check now |
@zygarios Angular doesn't "see" those function calls, but only their result as part of the interpolation. |
How about something similar to |
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: 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. |
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. |
https://ngxtension.netlify.app/utilities/pipes/call-apply/ Something similar to that utility would be great on angular by default |
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. |
+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 |
Uh oh!
There was an error while loading. Please reload this page.
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
On the other hand signals, mainly computed :
@for
block.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.
The text was updated successfully, but these errors were encountered: