Современный Angular: Заменяем жизненные циклы на сигналы

от автора

Если вы пишете на Angular, то наверняка часто используете хуки жизненного цикла вроде ngOnChangesngOnInit и ngOnDestroy. С появлением сигналов и концепции Zoneless (когда Zone.js уже не обязателен) у нас появились более элегантные и читаемые альтернативы.

Давайте разберем, как современный подход позволяет упростить код и избавиться от «шумных» методов жизненного цикла.

1. Вместо ngOnChanges — computed()

Было: классический подход с ngOnChanges

@Component({...})export class PricingComponent implements OnChanges {  @Input() price = 0;  totalPrice = 0;  constructor(private taxService: TaxService) {}  ngOnChanges(changes: SimpleChanges) {    if (changes['price']) {      // При обновлении инпута дергаем сервис      this.totalPrice = this.taxService.calculateTotal(this.price);    }  }}

Стало: реактивно с сигналами

@Component({...})export class PricingComponent {  // Сигнальный вход  price = input<number>(0);  private taxService = inject(TaxService);  // Реактивно вычисляем общую цену при каждом изменении price  totalPrice = computed(() => this.taxService.calculateTotal(this.price()));}

Плюсы: короче, читабельно, более производительно (вычисляется только когда реально нужно).

2. Отказываемся от ngOnInit в пользу декларативной инициализации

ngOnInit часто использовали как «безопасную» точку, где входные параметры уже гарантированно доступны. С сигналами мы можем инициализировать данные прямо на уровне свойств, реактивно.

Было: инициализация в ngOnInit

@Component({...})export class UserComponent implements OnInit {  @Input() userId!: string;  userData!: User;  constructor(private userService: UserService) {}  ngOnInit() {    // Ждем, пока Angular установит userId    this.userData = this.userService.getUserSync(this.userId);  }}

Стало: сигнальный подход

@Component({...})export class UserComponent {  userId = input.required<string>(); // обязательный вход  private userService = inject(UserService);  // Данные пользователя вычисляются реактивно при каждом изменении userId  userData = computed(() => this.userService.getUserSync(this.userId()));}

Дополнительный бонус: теперь при изменении userId (если такое возможно) данные обновятся автоматически — без лишних телодвижений.

3. Убиваем ngOnDestroy с помощью takeUntilDestroyed()

Больше не нужно вручную хранить массивы подписок (Subscription[]) и отписываться в ngOnDestroy. Спасибо оператору takeUntilDestroyed().

Было: ручное управление подпиской

@Component({...})export class AlertComponent implements OnDestroy {  private sub: Subscription;  constructor(private alertService: AlertService) {    this.sub = this.alertService.alerts$      .subscribe(alert => console.log(alert));  }  ngOnDestroy() {    this.sub.unsubscribe();  }}

Стало: автоматическая отписка через pipe

@Component({...})export class AlertComponent {  constructor(private alertService: AlertService) {    this.alertService.alerts$      .pipe(takeUntilDestroyed())   // Angular сам отпишет при уничтожении компонента      .subscribe(alert => console.log(alert));  }}

Код стал лаконичнее, и риск забыть про unsubscribe исчезает.(А если совсем перейти с RxJS на сигналы, то можно сделать еще проще — но это уже тема для отдельной статьи.)

Итог: жизненные циклы не умерли, но стали специализированным инструментом

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

Означает ли это, что жизненные циклы полностью уходят в прошлое? Нет. Они остаются для специфических задач — например, ngAfterViewInit всё ещё незаменим, когда нужна прямая работа с DOM или canvas.

Но как инструмент «на каждый день» — да, хуки успешно заменяются современными реактивными примитивами.

А что думаете вы?

Переход на сигналы — это действительно эволюция или просто ещё один «очередной способ писать то же самое»? Сталкивались ли вы с подводными камнями при миграции с жизненных циклов на computed и takeUntilDestroyed?

Особенно интересно услышать мнение тех, кто уже пробовал Zoneless-подход в боевых проектах:

  • Какие грабли успели собрать?

  • В каких кейсах без старых хуков всё-таки не обойтись?

  • Как оцениваете производительность после перехода?

А если есть чем поделиться или возразить — добро пожаловать в комментарии. Живое обсуждение — лучший способ разобраться в новой парадигме.

ссылка на оригинал статьи https://habr.com/ru/articles/1040488/