Using Signal and Model Inputs in Angular

Signal mechanism

In the Angular development strategy for the next 10 years, a special point is the implementation of Zoneless Change Detection – a mechanism for detecting dependencies without using the zone.js library. Partly for this purpose, the signal mechanism was developed, about which a decent number of articles have already been written.

In short, signals are a special subsystem that allows an application to subscribe to state updates in order to optimize rendering, without forcing the developer to worry about deleting subscriptions, as is the case when using RxJS.

The signals API is quite advanced and in addition to the most popular computed and effect methods, it also contains little-studied methods for determining incoming and outgoing properties, a remarkable feature of which is the creation of Signal-type objects on the fly.

Warning: Signal and Model Inputs are in Developer Preview, and while preliminary complete, using these parts of the API may require additional changes in the future.

What are Signal Inputs?

Signal Inputs can be thought of as a replacement for the Input decorator, with much of the same capabilities that using that classic decorator provides.

Example using the classical approach:

import { Component, Input } from '@angular/core';

@Component({ … })
export class ItemComponent {
  @Input({ required: true }) price!: number;
  @Input() discount = 0;
}

And this is how the same component will look, but using Signal Input:

import { Component, input } from '@angular/core';

@Component({ … })
export class ItemComponent {
  readonly price = input.required<number>();
  readonly discount = input(0);
}

The main advantage is that Angular will take care of checking for changes on its own. Thanks to the point approach, there will be no need to describe additional code in ngOnChanges or the corresponding setter. All that remains for us to do is to write our own effect, computed value or value output in the template.

Signal Inputs are a kind of reactive replacement for the classic Input decorator approach.

Like the Input decorator, Signal Inputs can be aliased. This is useful, for example, if you want to mark signal variables in your code in a special way, similar to Finnish notation to denote Observable. Opinions on this matter divergebut for myself I decided to mark them with a double dollar sign, similar to the single dollar sign for Observable, but to differentiate them:

price$$ = input.required<number>({ alias: 'price' })

What are Model Inputs

At first glance, it may seem that Model Inputs are a repeat of the existing ngModel mechanism. In fact, Model Inputs are an alternative to the Output decorator, since they can also be used to send data to the parent component.

Another great thing about Model Inputs is the ability to have two-way data binding without fear of change detection, coupled with the ability to create signal-based effects and calculations, which is essentially what they are.

I will show with examples the difference in approaches to using the Output and Model Inputs decorator.

Example using the classical approach:

import { Component, Input, Output } from '@angular/core';

@Component({ … })
export class ItemComponent {
  @Input({ required: true }) count!: number;
  @Output() countChange = new EventEmitter<number>();

  increment(): void {
    this.count = this.count + 1;
    this.countChange.emit(this.count);
  }
}

Here is an example of using Model Inputs:

import { Component, model } from '@angular/core';

@Component({ … })
export class ItemComponent {
  readonly count = model.required<number>();

  increment(): void {
    this.count.update(value => value + 1);
  }
}

In this case, the component template code is identical for both cases:

<app-item-component [(count)]="myCount" />

The code is similar, but in the case of Model Inputs, it is more compact and, most importantly, reactive, since in the case of the Output decorator, we also get problems with detecting changes, which will have to be solved separately. For example, by introducing reactivity through BehaviorSubject and splitting the shortcut. [(count)]="myCount" on [count]="myCount.value" And (countChange)="myCount.next($event)".

The developer only needs to write an effect on the changed value of the signal, describe the calculated value to create another signal, or simply display it on the screen: Angular itself will take care of detecting all changes.

As with Signal Inputs, you can specify a property alias for Model Inputs:

readonly count$$ = model.required<number>({ alias: 'count'})

Conclusion

In my opinion, the new Signal and Model Inputs functions are a great alternative to the classic Input and Output decorators. They solve the important problem of change detection without forcing you to write additional code for it.

Signals can be a bit confusing to use at first, but the more you delve into their philosophy, the simpler, clearer, and more logical the code becomes.

To demonstrate the difference in approaches, I have prepared a small demo application illustrating two components of identical functionality, but written in the classical and new ways:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *