Почему ngOnInit не вызывается в сервисе Angular при инжекции?
Почему 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
}
]
};
Этот подход гарантирует, что сервис будет полностью инициализирован до старта приложения. Однако помните: злоупотреблять им не стоит, так как он задерживает загрузку приложения. Используйте его только для критических данных (например, конфигурация, токены аутентификации).