Signals in Angular components

Signal API being now stable and out of developer preview, I tend to use it as much as possible when defining my component input's and output's (along with viewChild, viewChildren, contentChild and contentChildren).

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

@Component({
  selector: 'app-button',
  template: `
    <button (click)="onClick()">
        {{ label() }}
    </button>
  `,
})
export class ButtonComponent {
  label = input.required<string>();
  tooltipConfig = input.required<{ isConfirmable: boolean; /* other properties to style the tooltip */ }>();

  clicked = output<void>();

  onClick() {
    this.clicked.emit(); // Signal outputs keep the same API than @Output ones.
  }
}

Now, let's say we want to create a tooltip variable that will contain the title to display whether the button is confirmable or not (in tooltipConfig input).

Before signals, we would have implemented the OnChanges lifecycle hook and used the ngOnChanges method to detect when one or both of the component inputs had changed, like:

ngOnChanges(changes: SimpleChanges) {
    if (!changes.tooltipConfig) {
        return;
    }
    const { previousValue, currentValue } = changes.tooltipConfig;
    // Compare previousValue and currentValue to check if the value of tooltipConfig changed
}

Now, we can create a computed signal from the input signal:

tooltip = computed(() => {
    const label = this.label(); //  Will be triggered every time label changes.
    const isConfirmable = this.tooltipConfig().isConfirmable; // Will be triggered every time tooltipConfig changes.
    return isConfirmable ? `${label} - You will need to confirm first` : label;
})

The tooltip computed signal will be re-computed each time one of the input changes (as in the ngOnChanges method).