
Если Вы до сих пор не перешли с *ngIf, @Input() и ChangeDetectorRef на @if и signal/computed/effects, но подумываете об этом, то прошу под кат. Предполагается, что вы уже имеете опыт в Angular. В статье - небольшая выжимка практического опыта.
Немного философии
На мой взгляд, основное отличие современного Angular от старых версий 2+ является подход к детекции изменений: раньше в типовом случае нужно было либо использовать стратегию по-умолчанию (автодетекция изменений при любом событии), либо включать режим детекции при изменении входных параметров через changeDetection: ChangeDetectionStrategy.OnPush. В последнем случае, нередко приходилось форсить детекцию через ChangeDetectorRef.MarkForChanges() для не совсем тривиальных случаев.
Новый подход предполагает использования сигналов - обёрток над значениями, при изменении которых Angular понимает, что ему нужно перестроить HTML-дерево.
Главное: сигналы вместо обычных значений
Практически все обычные значения в вашем компоненте превратятся в сигналы:
@Component({
selector: 'my-component',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, // фактически standalone-компоненты - новый стандарт
...
})
class MyComponent
{
// так теперь инжектятся сервисы (раньше - через параметры конструктора)
private readonly myService = inject(MyService);
private readonly cdr = inject(ChangeDetectorRef); // такое теперь редко нужно
// опциональный входной параметр с заданным значением по-умолчанию
readonly myInputA = input('abc'); // Signal<string>
// обязательный входной параметр
readonly myInputB = input.required<number>(); // Signal<number>
// выходной параметр (они всегда опциональны)
readonly myOutput = output<number>();
// просто числовое значение, используемое в HTML-шаблоне
protected readonly myVal = signal(0); // WritableSignal<number>
// вычисляемое значение - будет автоматически перевычисляться
// при изменении myInputA или myInputB
protected readonly myCalсValue = computed(() => this.myInputA() + this.myInputB());
constructor() {
effect(() => {
// эффект - будет запускаться при изменении myCalValue
// (а также один раз сразу при объялении)
console.log("myCalcValue", this.myCalcValue());
});
}
protected onMyButtonClick() {
this.myVal.set(this.myVal() + 1);
// пример отправки выходного события
this.myOutput.emit(this.myVal());
}
}В HTML-шаблоне Вы теперь сможете использовать объявленные сигналы myInputA(), myInputB(), myVal()и myCalcValue(), что позволит Вам положиться на автоматическое перестроение HTML-дерева.
computed() стоит использовать для вычисления значений, которые зависят от других сигналов. При первом обращении Angular вызовет заданную в параметре ф-ию и запомнит, от каких сигналов зависит данное computed-значение. Если какой-то из этих сигналов изменится - ф-ия из computed будет вызвана для перерасчёта значения.
effect() работает похожим образом для случая, когда Вам не нужно возвращаемое значение, а нужны какие-то побочные эффекты. Обычно, это либо вызов/пересоздание каких-то низкоуровневых компонентов (какие-нибудь таблицы из Material, например), либо реакция на изменение значения в сигнале - например, выдача пользователю уведомления или даже как в примере выше - логирование изменения.
Косметические изменения в HTML-шаблонах
Мета-директивы *ngIf, *ngFor и прочие из CommonModule можно теперь заменить на встроенные конструкции:
<div>
@if (myVal() > 4) {
<span>Больше 4-х</span>
} @else {
<span>Меньше либо равно 4-м</span>
}
</div>
<ul>
<!-- здесь myItems - сигнал, хранящий массив объектов { id, name }-->
@for (item of myItems(); track item.id) {
<li>{{ item.name }}</li>
} @empty {
<li>Элементов не найдено.</li>
}
</ul>Standalone-компоненты
Много лет назад, в ещё тогда бета-версиях Angular 2, не существовало понятия "модуль" (ngModule), а все зависимости у компонентов указывались прямо в декораторе @Component. Через небольшой период времени разработчики фреймворка решили, что это неудобно и ввели известные нам модули - как сборники зависимостей. Прошло ~10 лет и история сделала оборот - теперь указывать все зависимости напрямую вновь стало нормальной практикой.
Сначала разработчики ввели для этого параметр standalone у декоратора @Component, который был false по-умолчанию. А начиная, кажется, с 19-ой версии зн��чение этого параметра стало по-умолчанию true.
Всегда казалось, что прямые зависимости - это хорошо и правильно. Рад, что разработчики одумались :)
Заключение
Мой личный опыт: сигналы и standalone-компоненты - это хорошо. К сигналам нужно привыкнуть. Часто сигналы заменяют рассылку событий, чем сильно упрощают жизнь - получает эдакая реактивность из коробки. Остальное - больше косметические изменения, которые мало на что влияют, но делают код чуть чище - и на том спасибо.
Напоследок желаю всем оставаться в тренде, пока нас ещё не заменил ИИ :-)
