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

Напоследок желаю всем оставаться в тренде, пока нас ещё не заменил ИИ :-)