Механизм сигналов

В стратегии развития Angular на ближайшие 10 лет особым пунктом стоит внедрение Zoneless Change Detection — механизма обнаружения зависимостей без применения библиотеки zone.js. Частично именно ради этой цели был разработан механизм сигналов, о которых уже написано приличное количество статей.

Если кратко, то сигналы — это специальная подсистема, с помощью которой приложение подписывается на обновление состояния с целью оптимизации рендеринга, при этом не заставляя разработчика переживать об удалении подписок, как это нужно делать при использовании RxJS.

API сигналов достаточно продвинут и помимо самых популярных методов computed и effect содержит еще малоизученные методы определения входящих и исходящих свойств, замечательная особенность которых — создание объектов типа Signal прямо на лету.

Предупреждение: Signal и Model Inputs находятся в стадии Developer Preview, и, несмотря на предварительную завершенность, использование этих частей API может потребовать дополнительных изменений в будущем.

Что такое Signal Inputs

Signal Inputs можно расценивать как замену декоратору Input с практически теми же возможностями, которые предоставляет использование этого классического декоратора.

Пример с использованием классического подхода:

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

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

А вот так будет выглядеть тот же компонент, но с использованием Signal Input:

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

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

Главное преимущество в том, что Angular самостоятельно позаботится о проверке изменений. Благодаря точечному подходу не нужно будет описывать дополнительный код в ngOnChanges или соответствующем сеттере. Все, что нам останется сделать, — написать свой собственный эффект, вычисляемое значение или вывод значения в шаблоне.

Signal Inputs — своего рода реактивная замена классического подхода с декоратором Input.

Подобно декоратору Input, для Signal Inputs возможны указания псевдонимов. Это удобно, например, если вы хотите особым образом помечать сигнальные переменные в коде, аналогично финской нотации для обозначения Observable. Мнения на этот счет расходятся, но для себя я решил отмечать их с двойным знаком доллара, подобно одиночному знаку доллара для Observable, но чтобы различать их:

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

Что такое Model Inputs

На первый взгляд может показаться, что Model Inputs — повторение существующего механизма ngModel. На самом же деле Model Inputs — альтернатива декоратору Output, так как с их помощью также можно отправить данные родительскому компоненту.

У Model Inputs есть еще одна замечательная особенность — возможность организации двустороннего связывания данных без страха за обнаружение изменений вкупе с возможностью создания эффектов и вычислений на основе сигнала, которыми, по сути, они и являются.

Покажу на примерах разницу в подходах использования декоратора Output и Model Inputs.

Пример с использованием классического подхода:

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);
  }
}

А вот пример использования 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);
  }
}

При этом код шаблона компонента идентичен для обоих случаев:

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

Код получается похожим, но в случае с Model Inputs — более компактным и, что самое важное, реактивным, так как в случае с Output‑декоратором мы в довесок получаем проблемы обнаружения изменений, которые уже придется решать отдельно. Например, внесением реактивности через BehaviorSubject и разделением шортката [(count)]="myCount" на [count]="myCount.value" и (countChange)="myCount.next($event)".

Разработчику достаточно написать эффект на измененное значение сигнала, описать вычисляемое значение для создания другого сигнала или просто вывести его на экран: об обнаружении всех изменений позаботится сам Angular.

Как и в случае с Signal Inputs, для Model Inputs можно указать псевдоним свойства:

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

Заключение

По моему мнению, новые функции Signal и Model Inputs — отличная альтернатива классическим декораторам Input и Output. Они решают важную проблему обнаружения изменений и не принуждают писать для этого дополнительный код.

Сигналы могут вызывать некоторые сложности в начале использования, но чем больше погружаешься в их философию, тем проще, понятнее и логичнее становится код.

В качестве демонстрации разницы подходов я подготовил небольшое демоприложение, иллюстрирующее два компонента идентичной функциональности, но написанные классическим и новым способами:

Комментарии (2)


  1. anonym0use
    13.08.2024 23:43

    Спасибо за публикацию, как обновимся - попробуем.


  1. xakboard
    13.08.2024 23:43

    Спасибо за статью