
Если Вы до сих пор не перешли с *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-компоненты - это хорошо. К сигналам нужно привыкнуть. Часто сигналы заменяют рассылку событий, чем сильно упрощают жизнь - получает эдакая реактивность из коробки. Остальное - больше косметические изменения, которые мало на что влияют, но делают код чуть чище - и на том спасибо.
Напоследок желаю всем оставаться в тренде, пока нас ещё не заменил ИИ :-)