JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Основные хуки жизненного цикла:

Основные хуки жизненного цикла: ngOnInit, ngOnChanges, ngOnDestroy

Модуль 4: Node.js, Next.js и Angular
14 уровень , 8 лекция
Открыта

1. ngOnInit: когда компонент готов к работе

Любой компонент Angular проходит через ряд этапов жизни: он появляется на свет (создается), получает входные данные, появляется на экране, обновляется при изменении входных данных, а потом уходит в "лучший мир" (удаляется из DOM). На каждом из этих этапов фреймворк дает вам возможность "вмешаться" — выполнить свой код в нужный момент. Такие специальные методы называются хуками жизненного цикла (lifecycle hooks).

Если проводить аналогию с театром: у актера есть репетиция, выход на сцену, аплодисменты, и, наконец, уход за кулисы. Angular — строгий режиссер, который всегда зовет вас на сцену вовремя, если вы объявили нужный "хук".

В этой лекции мы рассмотрим три самых важных хука, без которых не обходится почти ни один реальный компонент:

  • ngOnInit — инициализация компонента
  • ngOnChanges — реагирование на изменения входных данных
  • ngOnDestroy — уборка за собой перед уничтожением

Что это такое?

ngOnInit — это метод, который автоматически вызывается Angular сразу после того, как компонент создан и все его входные свойства (@Input) и зависимости инициализированы. Это отличное место для:

  • Первичной загрузки данных (например, запрос к серверу)
  • Инициализации переменных, которые зависят от входных данных
  • Запуска таймеров, подписки на события и т.д.

Чтобы использовать этот хук, ваш компонент должен реализовать интерфейс OnInit (это не обязательно, но очень рекомендуется для читаемости и автокомплита в редакторе).

Пример: Использование ngOnInit

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-greeting',
  template: `<p>{{ greeting }}</p>`
})
export class GreetingComponent implements OnInit {
  greeting = '';

  ngOnInit() {
    // Здесь компонент уже полностью создан и все @Input() доступны
    this.greeting = 'Добро пожаловать в Angular!';
    // Можно делать запросы к серверу, запускать подписки и т.д.
  }
}

Заметка:
Конструктор компонента вызывается раньше, но в нем еще не будут доступны значения @Input(). Вся логика, которая зависит от входных данных, должна быть в ngOnInit или позже.

Когда срабатывает ngOnInit?

  • После инициализации всех данных, но до первого отображения компонента на экране.
  • Вызывается только один раз за жизнь компонента.

// Шутка: Если вы перенесете загрузку данных в конструктор, Angular вас не накажет, но коллеги — могут. Не стоит так делать!

2. ngOnChanges: реагируем на изменения входных данных

Что это такое?

ngOnChanges — это метод, который вызывается каждый раз, когда Angular обнаруживает изменение одного или нескольких входных свойств (@Input()) компонента. Это особенно важно для вложенных компонентов, которые должны реагировать на изменения данных, пришедших от родителя.

Метод получает один параметр — объект типа SimpleChanges, в котором содержится информация о новых и старых значениях всех измененных входных свойств.

Пример: Использование ngOnChanges

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-score',
  template: `<p>Текущий счет: {{ score }}</p>`
})
export class ScoreComponent implements OnChanges {
  @Input() score = 0;

  ngOnChanges(changes: SimpleChanges) {
    if (changes['score']) {
      const prev = changes['score'].previousValue;
      const curr = changes['score'].currentValue;
      console.log(`Счет изменился с ${prev} на ${curr}`);
    }
  }
}

Как это работает?

  • Каждый раз, когда родительский компонент меняет значение score, Angular вызывает ngOnChanges в дочернем компоненте.
  • В объекте changes можно посмотреть, что именно изменилось, и как (старое и новое значение).

Когда используется ngOnChanges?

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

Важно: ngOnChanges вызывается даже раньше, чем ngOnInit — сразу после инициализации входных свойств.

Пример из жизни

Допустим, у вас есть компонент <app-user-card [userId]="selectedUserId"></app-user-card>. Каждый раз, когда пользователь выбирает другого пользователя, Angular вызовет ngOnChanges в UserCardComponent, и вы сможете загрузить новую информацию о пользователе.

3. ngOnDestroy: уборка за собой

Что это такое?

ngOnDestroy — это метод, который Angular вызывает непосредственно перед тем, как удалить компонент из DOM и освободить занимаемые им ресурсы. Здесь вы должны:

  • Отписываться от всех подписок (Observable, EventEmitter и т.д.)
  • Очищать таймеры и интервалы (setTimeout, setInterval)
  • Удалять слушателей событий, если добавляли их вручную

Если этого не делать, то в приложении могут возникнуть "утечки памяти" — неиспользуемые объекты останутся в памяти браузера, и приложение начнет тормозить (или даже падать).

Пример: Использование ngOnDestroy

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-timer',
  template: `<p>Секундомер: {{ seconds }} сек.</p>`
})
export class TimerComponent implements OnInit, OnDestroy {
  seconds = 0;
  private intervalId: any;

  ngOnInit() {
    this.intervalId = setInterval(() => {
      this.seconds++;
    }, 1000);
  }

  ngOnDestroy() {
    // Очищаем таймер, чтобы не было утечки памяти
    clearInterval(this.intervalId);
    console.log('Таймер уничтожен');
  }
}

Не забудьте:
Если вы подписываетесь на Observable (например, через RxJS), обязательно отписывайтесь в ngOnDestroy:

export class TimerComponent implements OnInit, OnDestroy {
  private subscription: Subscription;

  ngOnInit() {
    this.subscription = someObservable.subscribe(data => {
      // обработка данных
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Когда вызывается ngOnDestroy?

  • Каждый раз, когда Angular удаляет компонент из DOM (например, при смене маршрута, условном отображении через *ngIf и т.д.)
  • Вызывается только один раз за жизнь компонента.

4. Практика: Реализация простого приложения с хуками

Давайте разовьем наше учебное приложение. Представим, что у нас есть компонент, который отображает информацию о выбранном пользователе и обновляет счетчик времени, сколько эта карточка видна на экране.

Родительский компонент (упрощенно):

@Component({
  selector: 'app-root',
  template: `
    <button (click)="selectUser(1)">Пользователь 1</button>
    <button (click)="selectUser(2)">Пользователь 2</button>
    <app-user-card [userId]="selectedUserId"></app-user-card>
  `
})
export class AppComponent {
  selectedUserId = 1;

  selectUser(id: number) {
    this.selectedUserId = id;
  }
}

Дочерний компонент с хуками:

import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-user-card',
  template: `
    <div>
      <p>Пользователь: {{ userId }}</p>
      <p>Время на экране: {{ seconds }} сек.</p>
    </div>
  `
})
export class UserCardComponent implements OnInit, OnChanges, OnDestroy {
  @Input() userId!: number;
  seconds = 0;
  private intervalId: any;

  ngOnInit() {
    this.seconds = 0;
    this.intervalId = setInterval(() => this.seconds++, 1000);
    console.log('UserCardComponent инициализирован');
  }

  ngOnChanges(changes: SimpleChanges) {
    if (changes['userId'] && !changes['userId'].firstChange) {
      console.log('userId изменился:', changes['userId'].previousValue, '→', changes['userId'].currentValue);
      this.seconds = 0; // сбрасываем таймер при смене пользователя
    }
  }

  ngOnDestroy() {
    clearInterval(this.intervalId);
    console.log('UserCardComponent уничтожен');
  }
}

Что происходит:

  • При первом появлении карточки вызывается ngOnInit, запускается таймер.
  • Если родитель меняет userId, срабатывает ngOnChanges, таймер сбрасывается.
  • Если компонент удаляется (например, через *ngIf), вызывается ngOnDestroy, таймер очищается.

5. Типичные ошибки при работе с хуками жизненного цикла

Ошибка №1: Логика, зависящая от входных данных, находится в конструкторе.
В конструкторе значения @Input() еще не определены. Не пытайтесь обращаться к ним — используйте для этого ngOnInit или ngOnChanges.

Ошибка №2: Забыли отписаться от подписок или не очистили таймеры в ngOnDestroy.
Это приводит к утечкам памяти и "призрачным" событиям, которые продолжают срабатывать даже после удаления компонента.

Ошибка №3: Не реализован интерфейс (OnInit, OnChanges, OnDestroy).
Angular все равно вызовет методы с правильными именами, но без интерфейса вы лишаетесь автокомплита и проверки типов. Лучше всегда явно реализовывать интерфейс.

Ошибка №4: Не обработан случай первого изменения в ngOnChanges.
В объекте SimpleChanges есть свойство firstChange, которое бывает полезно, чтобы отличать первое присваивание от последующих изменений.

Ошибка №5: Сброс состояния в неправильном хуке.
Если вы сбрасываете состояние в ngOnInit, а оно должно сбрасываться при каждом изменении входных данных — используйте ngOnChanges.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ