Hierarchical DI
Angular DI iyerarxik — root injector'dan boshlab component daraxti bo'ylab pastga qarab injector'lar zanjiri hosil bo'ladi. Bir xil service turli darajalarda turli instance bo'lishi mumkin.
DI iyerarxiyasi — root, module, component
Nima bu?
Angular injector'lar daraxti:
- Root injector —
providedIn: 'root'service'lar shu yerda (singleton). - Module/Route injector — lazy modul yoki
providersbilan modul darajasida. - Component injector — component
providersarray'i — component va uning bolalari uchun alohida scope.
Component dependency so'raganda Angular avval o'z injector'idan, keyin ota injector'lardan qidiradi (yuqoriga chiqadi).
Klassik capqon — lazy-loaded route/modul: agar providedIn: 'root' bo'lgan service lazy modulning o'z providers massivida qayta ko'rsatilsa, root singleton ustiga emas, balki shu lazy modul uchun yangi, alohida instance yaratiladi (root injector emas, module injector'da). Natijada ilova boshqa joyida root instance, lazy modul ichida esa boshqa instance ishlaydi — state "sinxronlanmaydi". Buni interview'da ko'pincha "nega mening singleton service ikkita nusxada ishlayapti?" savoli sifatida so'rashadi.
Kod misoli
@Injectable()
export class LoggerService {
constructor(public id: string = crypto.randomUUID()) {}
log(msg: string): void {
console.log(`[${this.id}] ${msg}`);
}
}
// Root — bitta instance
@Injectable({ providedIn: 'root' })
export class AppConfigService {
apiUrl = '/api';
}
// Component scope — har bir AdminPanel uchun alohida Logger
@Component({
selector: 'app-admin-panel',
standalone: true,
providers: [LoggerService], // yangi instance shu subtree uchun
template: `<app-admin-child />`,
})
export class AdminPanelComponent {
constructor(public logger: LoggerService) {}
}
@Component({
selector: 'app-admin-child',
standalone: true,
template: ``,
})
export class AdminChildComponent {
constructor(public logger: LoggerService) {} // ota bilan bir xil instance
}
Imtihonda
Savol: Root'dagi service va component providers dagi bir xil class — bir instancemi?
Javob: Yo'q. Component providers yangi injector yaratadi — subtree uchun alohida instance. Root singleton emas.
Savol: providedIn: 'root' service lazy-loaded modulning providers ro'yxatida ham ko'rsatilsa nima bo'ladi?
Javob: Root singleton bilan bir qatorda, shu lazy modul uchun ikkinchi, mustaqil instance yaratiladi — ikkalasi orasida state umumiy bo'lmaydi.
Yodlash uchun
providers: [] — component darajasida yangi instance
Nima bu?
Component metadata'sidagi providers: [SomeService] shu component va uning barcha child component'lari uchun yangi DI scope yaratadi. Har bir component instance'i o'z service instance'ini oladi. Form wizard, tab panel, route-specific state uchun foydali.
Kod misoli
@Injectable()
export class WizardStateService {
step = signal(1);
data = signal<Record<string, unknown>>({});
nextStep(): void {
this.step.update((s) => s + 1);
}
}
@Component({
selector: 'app-wizard',
standalone: true,
providers: [WizardStateService],
template: `
<p>Qadam: {{ state.step() }}</p>
<app-wizard-step />
<button (click)="state.nextStep()">Keyingi</button>
`,
})
export class WizardComponent {
state = inject(WizardStateService);
}
@Component({
selector: 'app-wizard-step',
standalone: true,
template: `<input (input)="state.data.update(d => ({...d, name: $event.target.value}))" />`,
})
export class WizardStepComponent {
state = inject(WizardStateService); // ota Wizard bilan bir xil instance
}
<!-- Ikkita wizard — alohida state -->
<app-wizard />
<app-wizard />
Imtihonda
Savol: @Injectable({ providedIn: 'root' }) bo'lgan service'ni component providers ga qo'shsak nima bo'ladi?
Javob: Component subtree uchun yangi instance yaratiladi — root singleton ustiga yoziladi (shadow). Tashqaridagi component'lar hali root instance'ni oladi.
Yodlash uchun
@SkipSelf, @Self, @Optional dekoratorlari
Nima bu?
Injector qidirish yo'nalishini boshqaruvchi parameter dekoratorlar:
@Self()— faqat o'z injector'idan qidir (ota injector'ga chiqma).@SkipSelf()— o'z injector'ini o'tkazib, ota injector'dan boshla.@Optional()— topilmasanullqaytar (xato emas).@Host()—@Self()dan farqli, o'z injector'ini ham qidiradi, lekin qidiruvni host component chegarasidan oshirmaydi (undan yuqoriga — masalan root injector'ga — chiqmaydi).
Kod misoli
@Injectable()
export class ThemeService {
theme = signal<'light' | 'dark'>('light');
}
@Component({
selector: 'app-themed-card',
standalone: true,
providers: [ThemeService],
template: `<app-themed-inner />`,
})
export class ThemedCardComponent {
constructor(@Self() public theme: ThemeService) {}
// Self — faqat shu component injector'idagi ThemeService
}
@Component({
selector: 'app-themed-inner',
standalone: true,
template: `<p>Tema: {{ parentTheme?.theme() ?? 'default' }}</p>`,
})
export class ThemedInnerComponent {
constructor(
@SkipSelf() @Optional() public parentTheme: ThemeService | null
) {}
// SkipSelf — o'z injector'ini o'tkaz, ota ThemedCard'dan ol
}
// CoreModule guard pattern — bu guard faqat @NgModule uchun ishlaydi,
// chunki Angular NgModule import qilinganda uni avtomatik instantiate qiladi
@NgModule({})
export class CoreModule {
constructor(@Optional() @SkipSelf() parent: CoreModule) {
if (parent) {
throw new Error('CoreModule ikki marta import qilinmasin');
}
}
}
Imtihonda
Savol: @Self() va @SkipSelf() qachon kerak?
Javob: @Self() — faqat shu darajadagi provider kerak (masalan, component o'zi provider). @SkipSelf() — o'zini o'tkazib ota/root'dan olish (CoreModule guard, parent service'ga murojaat).
Yodlash uchun
