Closed splincode closed 2 years ago
@robwormald this is similar to what we have discussed.
I'd like to see local pipes as well, but I disagree with the rationale -- it seems to suggest that pipes are the way to use pure functions in templates to prevent rapid invocation on every CD cycle. Pipes are pure, yes, but that doesn't mean that everything that we want to be pure should be solved by a pipe. Pipes should be used for end-user-friendly formatting of data (decimal pipe, date pipe, currency pipe), not for arbitrary invocation of pure functions.
The way I see it, this proposal asks about a way to define pure functions, but (wrongly, in my opinion) assumes that pipes are the way to go.
Instead of pipes, why don't we allow this:
@Pure()
public reverse(value: string) {
return value.split('').reverse().join('')
}
Then, in the template, we can just do, as always,
{{ reverse(msg) }}
but without the degrading performance implications.
When looked from this perspective, I don't think that you often actually want a local pipe. A pipe should be universal and should be able to format data from everywhere in the app -- there should be little reason to tie it to one specific component. It's not pipes that we need, it's pure functions.
A pipe should be universal and should be able to format data from everywhere in the app
Sure, but still there are many cases (at least at our environment) where local pipes are useful because they are exceptions for just that one component ...
@Pure() public reverse(value: string) { ... }
This way is certainly the preferred one but we have to also answer what should be the limits for input params ... the same logic as for OnPush
strategy? So immutable objects, arrays + primitives?
what should be the limits for input params
The same as with pipes, I guess?
I think that @lazarljubenovic nailed it in his comment:
I'd like to see local pipes as well, but I disagree with the rationale -- it seems to suggest that pipes are the way to use pure functions in templates to prevent rapid invocation on every CD cycle. Pipes are pure, yes, but that doesn't mean that everything that we want to be pure should be solved by a pipe. Pipes should be used for end-user-friendly formatting of data (decimal pipe, date pipe, currency pipe), not for arbitrary invocation of pure functions.
Going over this issue (and similar ones) I've realised that we are discussing 2 separate issues:
ability to define and use a pipe in one component only (without a need of registering it in NgModule
, sharing with other components etc.); the need here is to simplify pipes creation, especially when those are used in one component only;
make sure that (pure) functions are not repeatedly called in each and every change detection cycle; the need here is to avoid unnecessary work and (potential) performance issues.
From what I'm reading in other issues the (2) is what we are really after and (1) is just a mean to getting (2). While I agree that pure pipes could be used to limit unnecessary invocations of (pure) functions it sounds like we are after property of pure pipes (memorisation of results based on arguments) here. Using pure pipes could work but IMO it is unnecessary complication (one needs to create a class, register it in NgModule
, Angular would have create its instance, we need to take care of the this
etc.) for simply telling Angular "hey, this is a pure function, don't call it repeatedly if arguments are the same".
I totally see the need of better support for pure functions in Angular template syntax but as @lazarljubenovic I don't believe that pipes are the way to go. It would be just too heavy for what we really need.
BTW, the (1) need from the above discussion is a duplicate of (or at least very much related to) #24559
2. make sure that (pure) functions are not repeatedly called in each and every change detection cycle; the need here is to avoid unnecessary work and (potential) performance issues
Solvable with Observables. It's just a matter of getting used to it.
Just thought of this elegant little solution to the "pure function" issue. It's a good workaround for now. Actually, now that I think about it it somewhat solves the whole feature request since with this method you can now defer a pipe call to whatever function you want. You could even modify the pureCall pipe so it will defer to a class instance generated by the passed function if you needed a stateful transform pipe (although that would most likely defeat the whole purpose of making it a pipe since stateful transform sounds like "impure" to me).
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'pureCall' })
export class PureCallPipe implements PipeTransform {
transform(func:Function, ...args:any[]):any {
return func(...args);
}
}
And can be used like so
@Component({
selector: 'app-root',
template: `<ul><li *ngFor="let hash in imageHashes">{{hashToFile|pureCall:hash:'frank'}}</li></ul>`,
})
export class AppRoot{
imageHashes:string[] = ['fakehash', 'stillfake', 'whyareyoulookinghere-stillsuperfake'];
hashToFile(imageHash:string, name:string):string{
return `/user/${name}/${imageHash}.jpg`;
}
}
Enjoy!
Pipes are pure, yes, but that doesn't mean that everything that we want to be pure should be solved by a pipe. Pipes should be used for end-user-friendly formatting of data (decimal pipe, date pipe, currency pipe), not for arbitrary invocation of pure functions.
IMHO pipes have another major benefit, which is centralizing formatting logic for reusability. In our applications we have a lot of enums, and enums want to be displayed in a readable format or be mapped to a specific icon etc. Having pipes for these makes it very easy to manage this logic separately and DRY. The same could be achieved by services, but that would require manually injecting them everywhere.
→ I agree that not every single pure function should be a pipe, but I do think the market for pipes is or can be bigger than it seems.
I like the idea with icons, since an icon is still a formatting issue. You don't need the icon in the model, you only want to display it to the user from the template. Just like you transform 10000
into 10.000,00
, you also want to transform true
into a green-colored circle and false
into a red-colored circle. So I see <my-icon [icon]="user.isOnline | iconize"></my-icon>
conceptually equivalent to {{ user.postsCount | number : '1.0-0' }}
, even though one pipe returns a string and the other pipe returns (probably) and object with SVG data attached to it, and my-icon
knows how to turn this data into a valid HTML/SVG structure.
I hate to be that guy, but... any progress on this issue?
Currently working on something in my app that would be improved by a feature like this. I have a component that displays translated tooltip content based on the entered data. I think if you guys do plan to implement something like this to also consider idempotent functions rather than just pure functions. This applies in my case because I have to use a service to translate the result, but the result is consistently the same consistently with a given input.
I guess if it were impossible to handle idempotent functions, then I can pass the result of the pure function to a translate pipe. For other situations like this though, I would prefer to be able to do everything without a pipe if the function is available in the component.
Standalone components support locally-defined pipes, ex.:
@Pipe({ name: 'brackets', standalone: true })
export class ExamplePipe implements PipeTransform {
transform(value: any, ...args: any[]) {
return `(${value})`;
}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [ExamplePipe],
template: `{{'I am in brackets' | brackets}}`,
})
export class ExampleStandaloneComponent {}
Working stackblitz: https://stackblitz.com/edit/angular-standalone-ks9pkt?file=src%2Fmain.ts
Closing as already implemented.
Standalone components support locally-defined pipes, ex.:
@Pipe({ name: 'brackets', standalone: true }) export class ExamplePipe implements PipeTransform { transform(value: any, ...args: any[]) { return `(${value})`; } } @Component({ selector: 'app-root', standalone: true, imports: [ExamplePipe], template: `{{'I am in brackets' | brackets}}`, }) export class ExampleStandaloneComponent {}
Working stackblitz: https://stackblitz.com/edit/angular-standalone-ks9pkt?file=src%2Fmain.ts
Closing as already implemented.
I think it's not what people from this topic expected. We could do almost the same without standalone components. This is not ergonomic solution.
@evgeniyefimov the problem is that there are multiple items being discussed here so at this point it is not very clear what the expectation is.
But if people want to use "just a function in a template":
Is there more to it?
@evgeniyefimov the problem is that there are multiple items being discussed here so at this point it is not very clear what the expectation is.
But if people want to use "just a function in a template":
- for un-pure functions one can just call a function in a template - there is very little added value of a pipe here;
- for memoized "pure" functions one could use a memo utility as described in Computed properties (model => model synchronisation) #47553 (comment)
Is there more to it?
@pkozlowski-opensource Call a function in a template is a bad pratice. It'll be called each change detection cycle, but pipe only when its' inputs are changed.
About memoized functions @dawidgarus mentioned here https://github.com/angular/angular/issues/20419#issuecomment-425070292 that memoized function can't do things like: <div *ngFor="let item of list">{{func(item, arg2)}}</div>
. There is a discussion about this.
Would be great to have some really simple solution, just an example:
@Component({
...
})
export class Component {
sum(a: number, b: number): number {
return a + b;
}
<div>{{ sum | a : b }}</div>
It's a bad practice to do it mindlessly. There are countless cases where it's perfectly fine to do so and is no better than a pipe.
This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.
I'm submitting a...
Current behavior
app.component.ts
reverse.pipe.ts
Expected behavior
I. First expected
II. Second expected
I would also like to have provideIn for pipe. Because I can have many utilities and every time I import them tires.
What is the motivation / use case for changing the behavior?
According to statistics, very rarely projects are created by pipes
Quickly create piped methods
@StephenFluin @mhevery @trotyl Do you think this is possible?