Механизм сигналов
В стратегии развития 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. Они решают важную проблему обнаружения изменений и не принуждают писать для этого дополнительный код.
Сигналы могут вызывать некоторые сложности в начале использования, но чем больше погружаешься в их философию, тем проще, понятнее и логичнее становится код.
В качестве демонстрации разницы подходов я подготовил небольшое демоприложение, иллюстрирующее два компонента идентичной функциональности, но написанные классическим и новым способами:
anonym0use
Спасибо за публикацию, как обновимся - попробуем.