Computed properties (model => model synchronisation)
See original GitHub issueWhich @angular/* package(s) are relevant/related to the feature request?
core
Description
Angular dirty-checking mechanism works well for synchronising (propagating) model changes to the DOM. However, there are legitimate situations where one would like to compute / derive a new model value (from the existing model values) before rendering. Angular has no such concept - even if requested by users (ex.: #20472).
Proposed solution
We don’t have any concrete solution proposal at the moment but we are exploring the space and will open a RFC when the solution space becomes clearer. But even without the concrete proposal on a table, there are certain properties that we would like to see in the final solution:
- semantics of “recompute only if one of the dependencies changed”;
- composability: it should be possible to construct of chain of computed values (computed depending on other computed);
- great integration in the framework, ex.:
@Input
- it should be possible to compute new model value based on input values changing over time;- convenient syntax for accessing computed values in a component / teamplate.
Alternatives considered
While Angular doesn’t have the dedicated concept of the computed properties, there are several existing mechanisms that can serve the purpose (although with different caveats). Here is an overview of the possible approaches that work today.
Use an external reactivity system
Many Angular users integrate RxJS, or similar push-based reactivity systems, into their template rendering pipeline. Most (all?) of the push-based reactivity systems have a concept of deriving values (ex. combinelatest in RxJS).
Use memoization
We could introduce a simple memo
utility function. A memo function could just compare previous / new arguments and execute re-computation when needed, similar to what the pure pipe does today. Ex. (taken from #46135):
export class AppComponent {
fruit = 'banana';
idFruit = 0;
getNameFruitUrl = memo((name: string) => `/fruits/${name}.jpg`);
getIdFruitUrl = memo(() => `/fruits/${this.idFruit}.jpg`, () => [this.idFruit]);
getLargeFruitUrl = memo((large?: boolean) => `/${large ? 'large' : 'small'}/${this.fruit}.jpg`,
() => [this.fruit]
);
}
Here is a working stackblitz: https://stackblitz.com/edit/angular-ivy-txzpmf?file=src%2Fapp%2Fapp.component.ts,src%2Fapp%2Fmemo.ts
Existing issues / PRs
The idea of computed properties was discussed in the past in the issue tracker - most notably in #20472 (which generated number of comments and ~70 upvotes).
Issue Analytics
- State:
- Created a year ago
- Reactions:25
- Comments:8 (1 by maintainers)
Top GitHub Comments
I would strongly vote agains React’s
useEffect
-like manual list of dependencies - it’s a road to the dead-end as DX of this approach is terrible and this list is very difficult to keep in sync with the code in the memoized function.Deps should be collected automatically just like in the majority of reactive frameworks/libs.
In my opinion, having a decorator that turns an input into an observable would be fantastic.
It would:
Or with a more realistic example:
The only point I can think of that’d need a bit more thinking would be how to get an equivalent of
ngOnChanges
because if you’ve got plenty of inputs, and during a CD cycle you change them all, you do not want to use all the individual observables with acombineLatest
as it’d fire quite a few times. An easy workaround would be to usecombineLatest
but with adebounceTime
for example but DX wouldn’t be that good.And with this, would come the question of being type safe etc.
A possible alternative to that solution (for the previous example) would be something like: