Avatar ·

Почему ngOnInit не вызывается в сервисе Angular при инжекции?

Привет! Я новичок в Angular и TypeScript, и столкнулся со странной проблемой. У меня есть сервис, который должен выполнять какую-то инициализацию при создании. Я решил использовать Angular-хуки, в частности `ngOnInit`, чтобы этот код выполнялся автоматически. Но он не работает — метод `ngOnInit` просто не вызывается. Вот мой код: ```typescript import { Injectable, OnInit } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class DataService implements OnInit { public data: any[] = []; constructor() { console.log('Constructor called'); } ngOnInit(): void { console.log('ngOnInit called'); this.data = ['item1', 'item2']; } } ``` Я ожидаю, что когда сервис будет инжектирован в компонент, `ngOnInit` вызовется автоматически, но в консоли вижу только сообщение "Constructor called". Я читал документацию, но не могу понять, в чём ошибка. В компонентах `ngOnInit` работает отлично. Может быть, я неправильно импортирую интерфейс? Или нужно что-то дополнительно регистрировать? Подскажите, пожалуйста, в чём дело. <<>>

Почему ngOnInit не работает в сервисах?

Вы столкнулись с распространённым заблуждением начинающих Angular-разработчиков. Ключевой момент: Angular-хуки жизненного цикла (lifecycle hooks), включая ngOnInit, предназначены только для директив и компонентов. Сервисы, помеченные декоратором @Injectable, не являются частью иерархии компонентов и не участвуют в механизме обнаружения изменений Angular.

Когда Angular создаёт экземпляр компонента, он проходит через строго определённые фазы: создание, инициализация входных свойств, проверка изменений и т.д. Именно в этих фазах вызываются хуки вроде ngOnInit, ngOnChanges, ngAfterViewInit. Сервисы же — это просто классы, управляемые инжектором зависимостей. Инжектор создаёт экземпляр (вызывая конструктор) и кэширует его, но не запускает никаких lifecycle-методов.

Правильное решение: используйте конструктор

Для инициализации сервиса используйте конструктор. Это единственное место, которое гарантированно выполняется при создании сервиса:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  public data: any[] = [];

  constructor() {
    console.log('Constructor called');
    // Инициализация здесь
    this.data = ['item1', 'item2'];
    console.log('Data initialized in constructor');
  }
}

Альтернатива: метод инициализации с вызовом извне

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

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  public data: any[] = [];
  private initialized = false;

  constructor() {
    // Минимальная настройка
  }

  async initialize(): Promise<void> {
    if (this.initialized) return;
    this.initialized = true;
    
    // Асинхронная загрузка данных
    this.data = await this.fetchData();
    console.log('Service initialized');
  }

  private async fetchData(): Promise<any[]> {
    // Имитация HTTP-запроса
    return new Promise(resolve => {
      setTimeout(() => resolve(['item1', 'item2']), 1000);
    });
  }
}

В компоненте вызывайте этот метод в ngOnInit:

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-root',
  template: '...'
})
export class AppComponent implements OnInit {
  constructor(private dataService: DataService) {}

  async ngOnInit(): Promise<void> {
    await this.dataService.initialize();
    console.log('Service data:', this.dataService.data);
  }
}

Почему не стоит имитировать хуки?

Некоторые разработчики пытаются "подделать" поведение хуков, вызывая методы вручную или используя фабрики. Это усложняет код и нарушает принцип единой ответственности. Angular-хуки — это строгий контракт для компонентов, и их не следует применять к сервисам.

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

Итог

Уберите реализацию OnInit из сервиса и перенесите код инициализации в конструктор. Если нужна асинхронная загрузка — используйте отдельный метод. Помните: ngOnInit — это хук компонента, а не сервиса.

<<>>

Дополнительный подход: использование фабричного провайдера

Если вам критично, чтобы сервис инициализировался с асинхронными данными до того, как его начнут использовать компоненты, можно применить фабричный провайдер. Этот способ подходит для случаев, когда инициализация должна быть завершена до первого инжектирования:

import { Injectable, APP_INITIALIZER } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ConfigService {
  public config: any;

  constructor() {}

  async loadConfig(): Promise<void> {
    const response = await fetch('/api/config');
    this.config = await response.json();
  }
}

// Функция-фабрика для APP_INITIALIZER
export function initializeApp(configService: ConfigService) {
  return (): Promise<void> => configService.loadConfig();
}

// В модуле (или standalone-конфигурации)
export const appConfig = {
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initializeApp,
      deps: [ConfigService],
      multi: true
    }
  ]
};

Этот подход гарантирует, что сервис будет полностью инициализирован до старта приложения. Однако помните: злоупотреблять им не стоит, так как он задерживает загрузку приложения. Используйте его только для критических данных (например, конфигурация, токены аутентификации).