Chris Kohler

Navigate back to the homepage

Computed Properties in Angular

Chris Kohler
July 29th, 2022 · 4 min read

Table of Contents

  • Intro
  • TLDR;
  • What is a computed property?
  • Computed properties in Angular
  • Summary

Intro

Many frameworks and libraries have built-in support for computed properties.

For example Svelte:

1let firstName = „Chris“;
2let lastName = „Kohler“;
3$: fullName = `${firstName} ${lastName}`;

Or Vue:

1computed: {
2 fullName() {
3 return `${this.firstName} ${this.lastName}`
4 }
5}

Or MobX:

1class TodoList {
2 @observable firstName = „Chris“
3 @observable lastName = „Kohler“
4
5 @computed
6 get fullName() {
7 return `${firstName} ${lastName}`;
8 }
9}

So if you are coming from a framework that has a computed keyword, you might be wondering how to define computed properties in Angular.

TLDR;

There is no computed keyword or decorator in Angular. But there are many ways how to define computed properties in Angular. If you are already using observables, just use pipe(map). Otherwise, getters and pipes are a good way to define computed properties.

What is a computed property?

A computed property is a value that is derived from state.

For example, if you have a spreadsheet with a list of purchases and one line with the total cost, the purchases are state and the total cost is derived state.

excel

The advantage of defining computed properties is that when you change the state, the computed property is automatically updated. In this example when you change the cost of an item or add/remove an item, the total will be updated automatically.

Computed properties in Angular

There is no computed keyword or decorator in Angular. But that doesn‘t mean it is not possible to define computed properties. In fact, there are multiple ways to define them. Let‘s have a look at them.

We go through the following ways:

  • ngOnChanges
  • getters
  • pipe
  • RxJS
  • ngrx/component-store

Computed properties in “dumb” / presentational components

In this post, I focus on “dumb” components with computed properties. That’s where I most often use those patterns. For smart / container components I often use a state management library with built-in support for computed properties. That said, all the approaches can also be used for container components.

Example App

We use this simple interface for all the examples:

1interface Todo {
2 name: string;
3 done: boolean;
4}

In the examples, we implement the todo-stats component with the different approaches. The component is then used in the AppComponent as shown below:

1@Component({
2 template: `
3 <!-- Display: "Todos done: 5" -->
4 <todo-stats [todos]="todos"></todo-stats>
5 `,
6 selector: "my-app"
7})
8export class AppComponent {
9 todos: Todo[] = [
10 { name: "shop", done: false },
11 { name: "clean", done: true },
12 { name: "travel", done: true }
13 ];
14}

Also, all implementations can use this helper function to filter todos by the status “done”:

1function byDone(todo: Todo): boolean {
2 return todo.done;
3}

Approach with ngOnChanges

1@Component({
2 template: `
3 Todos done: {{ done }}
4 `,
5 selector: "todo-stats"
6})
7export class TodoStatsComponent implements OnChanges {
8 @Input() todos: Todo[] = [];
9
10 done = 0;
11
12 ngOnChanges(): void {
13 this.done = this.todos.filter(byDone).length;
14 }
15}

ngOnChanges is triggered when an @Input changes. Whenever this happens we make sure to update the done count.

This approach is simple and easy to understand. But only for small examples like this one. When the component grows, it is more difficult to see how the done property depends on ngOnChanges. This approach can also be error-prone since we have to always remember to update the done property whenever we change the todos. I try to avoid this approach for derived state. That said, in cases where I have many inputs it can be a good option to orchestrate.

  • ✅ Simple and easy to understand
  • ✅ Good for multiple inputs
  • ⚠️ Can be error-prone with a more complex components

Approach with a getter

1@Component({
2 template: `
3 Todos done: {{ done }}
4 `,
5 selector: "todo-stats"
6})
7export class TodoStatsComponent {
8 @Input() todos: Todo[] = [];
9
10 get done() {
11 return this.todos.filter(byDone).length;
12 }
13}

Compared to the ngOnChanges, this approach has a few advantages. First, we see directly how done is derived. Second, we never have to remember to update the done property when changing the todos.

This approach is very close to how we write formulas in a spreadsheet. One downside is, that every time the component is rerendered, the done property is recomputed. In this example, it is fine if the component is only rerendered when the todos array changes. This can be easily achieved by changing the change detection to OnPush.

I like the readability of getters and use is often for components where I don’t have any observables and only 1-2 inputs.

What about perfomance? You might think that this approach is bad for performance because there is no caching. That’s true, there is no caching. But in components with 1-2 inputs it is very likely that you anyway always have to update the derived property. Caching also adds some overhead and is not generally necessary for every computation.

  • ✅ Derived state is clearly defined
  • ✅ Getters is a fundamental language feature, no new concept to learn

Approach with an Angular Pipe

1@Pipe({ name: "numberOfDoneTodos" })
2export class DoneTodosPipe implements PipeTransform {
3 transform(todos: Todo[]): number {
4 return todos.filter(byDone).length;
5 }
6}
7
8@Component({
9 template: `
10 Todos done: {{ todos | numberOfDoneTodos }}
11 `,
12 selector: "todo-stats"
13})
14export class TodoStatsComponent {
15 @Input() todos: Todo[] = [];
16}

I like this approach for the very clear separation of concerns. Another plus is that (pure) pipes are cached by default. That means that property is no recomputed if the input didn’t change

  • ✅ Clear separation
  • ✅ Build in cache (pure pipes)

Approach with a generic Angular Pipe

The approach with an Angular pipe has the disadvantage that we need to write a new pipe for every filter. This results in quite some boilerplate.

Another option is to create a generic FilterPipe:

1@Pipe({ name: 'filter' })
2export class FilterPipe implements PipeTransform {
3 transform(array: any[], predicate: (value: any) => any): any {
4 return array.filter(predicate);
5 }
6}

This pipe can then be used with any filter predicate:

1@Component({
2 template: `
3 Todos done: {{ (todos | filter: byDone).length }}
4 `,
5 selector: 'todo-stats'
6})
7export class TodoStatsComponent {
8 @Input() todos: Todo[] = [];
9
10 byDone = (todo: Todo) => todo.done
11}

Thank you @NetanelBasal for the inspiration. I really like the idea. Keeps the component code very readable.

  • ✅ Readability
  • ✅ Build in cache (pure pipes)
  • ✅ Can be combined with other pipes

Approach with RxJS

1@Component({
2 template: `
3 Todos done: {{ done$ | async }}
4 `,
5 selector: "todo-stats"
6})
7export class TodoStatsComponent {
8 @Input() set todos(todos: Todo[]) {
9 this.todos$.next(todos);
10 }
11
12 todos$ = new BehaviorSubject<Todo[]>([]);
13
14 done$ = this.todos$.pipe(
15 map(todos => todos.filter(byDone).length)
16 );
17}

The reactive approach. This is my go-to solution if I am already in the RxJS world. Very powerful with many operators out of the box. For our simple example it might be overengineered but for more complex logic RxJS helps a lot to write clean code.

  • ✅ Recommended when part of the state is already in the RxJS world (observables)
  • ✅ Very powerful with many operators out of the box

Approach with ngrx/component-store

1@Component({
2 template: `
3 Todos done: {{ todosDone$ | async }}
4 `,
5 selector: "todo-stats",
6 providers: [ComponentStore]
7})
8export class TodoStatsComponent {
9 @Input() set todos(todos: Todo[]) {
10 this.todosStore.setState(todos);
11 }
12
13 todosDone$ = this.todosStore.select(
14 todos => todos.filter(byDone).length
15 );
16
17 constructor(
18 protected todosStore: ComponentStore<Todo[]>
19 ) {}
20}

Bonus approach. I see this approach as a variant of the RxJS approach. If you often use the RxJS approach, using the ngrx/component-store library can be a nice addition. ComponentStore is a standalone library that helps to manage local/component state and comes with methods like setState or select.

You can start with a generic store like I do in this example. When you see the component gets too complex, it is easy to extract the store and define selectors in the store instead of in the component itself.

  • ✅ Recommended as an alternative to the RxJS approach
  • ✅ Nice refactor options
  • select is in my opinion more readable than pipe(map)

Summary

There are many ways how to define computed properties in Angular. There is no right or wrong. I use almost all the approaches in my apps, often many approaches in the same app.

Here is how I choose between the approaches.

  1. If I am not using observables I either use the getter or pipe approach.
  2. If I am already using observables in a component, I stay in the RxJS world with either pure RxJS or ngrx/component-store.
  3. I rarely use ngOnChanges to update derived state.

Do you have another approach? Let me know on Twitter

Spread the word

Did you like the article? Please spread the word 🙌 and follow me on Twitter for more posts on web technologies.

Did you find typos 🤓? Please help improve the blogpost and open an issue here or post your feedback here

More articles from Chris Kohler

Angular View Engine was removed - What you need to know

What does the deprecation of the View Engine in Angular 13 mean for you?

November 15th, 2021 · 3 min read

Angular Modules Best Practices 2021

Best practices how to bundle your application with Angular modules

July 5th, 2021 · 5 min read
© 2024 Chris Kohler
Link to $https://twitter.com/kohlerchristianLink to $https://github.com/christiankohlerLink to $https://instagram.com/mrchriskohlerLink to $https://www.linkedin.com/in/mr-christian-kohler/