Просунутий Dependency Injection в Angular 2026: Providers, Tokens та Питання на Співбесідах
Комплексний посібник з просунутих механізмів Dependency Injection в Angular. Providers, InjectionToken, ієрархія інжекторів та найпоширеніші питання на технічних співбесідах.

Dependency Injection (DI) є фундаментом архітектури Angular і одним з найважливіших патернів проєктування, що використовуються у цьому фреймворку. Хоча базові концепції DI відносно прості для освоєння, просунуті техніки вимагають глибшого розуміння механізмів роботи інжекторів, провайдерів та токенів.
У цій статті розглядаються просунуті аспекти системи DI в Angular 19, які є ключовими як для щоденної роботи розробника, так і для технічних співбесід на позиції senior та lead developer.
Варто зазначити, що Angular 19 впровадив значні покращення в системі DI, включаючи кращу інтеграцію з сигналами та нові можливості конфігурації провайдерів. Знання цих нововведень може бути вирішальним на технічних співбесідах.
Ієрархія Інжекторів в Angular
Система DI в Angular базується на ієрархічній структурі інжекторів. Розуміння цієї ієрархії є фундаментальним для ефективного управління залежностями в застосунку.
Angular визначає три основні рівні інжекторів:
- Platform Injector - найвищий рівень, спільний для всіх Angular-застосунків на сторінці
- Root Injector - рівень застосунку, створюється під час bootstrap
- Element Injector - рівень компонента або директиви
// Приклад ієрархії інжекторів
@Injectable({
providedIn: 'root' // Реєстрація в Root Injector
})
export class GlobalConfigService {
readonly apiUrl = 'https://api.example.com';
}
@Injectable()
export class FeatureService {
// Може бути зареєстрований на рівні модуля або компонента
}
@Component({
selector: 'app-feature',
providers: [FeatureService], // Element Injector
template: `<div>Feature Component</div>`
})
export class FeatureComponent {
constructor(
private globalConfig: GlobalConfigService,
private featureService: FeatureService
) {}
}Процес розв'язання залежностей починається з найближчого інжектора і просувається вгору по ієрархії до знаходження відповідного провайдера або генерування помилки.
Типи Провайдерів та їх Застосування
Angular пропонує різноманітні способи конфігурації провайдерів, кожен з унікальними випадками використання.
useClass - Базова Реалізація
// Базове використання useClass
const providers = [
{ provide: LoggerService, useClass: LoggerService },
// Скорочена форма:
LoggerService
];
// Заміна реалізації
@NgModule({
providers: [
{ provide: LoggerService, useClass: AdvancedLoggerService }
]
})
export class AppModule {}useValue - Статичні Значення
// Конфігурація застосунку як значення
export interface AppConfig {
apiUrl: string;
debugMode: boolean;
maxRetries: number;
}
export const APP_CONFIG: AppConfig = {
apiUrl: 'https://api.example.com',
debugMode: false,
maxRetries: 3
};
// Токен для конфігурації
export const APP_CONFIG_TOKEN = new InjectionToken<AppConfig>('app.config');
// Реєстрація
@NgModule({
providers: [
{ provide: APP_CONFIG_TOKEN, useValue: APP_CONFIG }
]
})
export class AppModule {}
// Використання
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(@Inject(APP_CONFIG_TOKEN) private config: AppConfig) {
console.log(this.config.apiUrl);
}
}useFactory - Динамічне Створення Екземплярів
// Factory із залежностями
export function loggerFactory(
http: HttpClient,
config: AppConfig
): LoggerService {
if (config.debugMode) {
return new DebugLoggerService(http);
}
return new ProductionLoggerService(http);
}
@NgModule({
providers: [
{
provide: LoggerService,
useFactory: loggerFactory,
deps: [HttpClient, APP_CONFIG_TOKEN]
}
]
})
export class AppModule {}useExisting - Псевдоніми Сервісів
// Створення псевдоніма для існуючого сервісу
@Injectable({ providedIn: 'root' })
export class AuthService {
isAuthenticated(): boolean { return true; }
}
abstract class AuthChecker {
abstract isAuthenticated(): boolean;
}
@NgModule({
providers: [
{ provide: AuthChecker, useExisting: AuthService }
]
})
export class AppModule {}InjectionToken - Безпечна Типізація
InjectionToken забезпечує безпечний спосіб визначення токенів для значень, що не є класами.
// Визначення токенів з типовими значеннями
export const API_BASE_URL = new InjectionToken<string>('api.baseUrl', {
providedIn: 'root',
factory: () => 'https://api.default.com'
});
export const FEATURE_FLAGS = new InjectionToken<Map<string, boolean>>(
'feature.flags',
{
providedIn: 'root',
factory: () => new Map([
['darkMode', true],
['newDashboard', false]
])
}
);
// Токен із залежністю від інших сервісів
export const COMPUTED_CONFIG = new InjectionToken<ComputedConfig>(
'computed.config',
{
providedIn: 'root',
factory: () => {
const env = inject(EnvironmentService);
return {
apiUrl: env.isProd ? 'https://api.prod.com' : 'https://api.dev.com',
timeout: env.isProd ? 5000 : 30000
};
}
}
);Функція inject() та Сучасні Патерни
Angular 19 просуває використання функції inject() як переважного методу впровадження залежностей.
// Традиційне впровадження через конструктор
@Injectable({ providedIn: 'root' })
export class TraditionalService {
constructor(
private http: HttpClient,
private logger: LoggerService
) {}
}
// Сучасний підхід з inject()
@Injectable({ providedIn: 'root' })
export class ModernService {
private http = inject(HttpClient);
private logger = inject(LoggerService);
// Умовне впровадження
private optionalService = inject(OptionalService, { optional: true });
}
// inject() у допоміжних функціях
export function createDataFetcher<T>(url: string) {
const http = inject(HttpClient);
const errorHandler = inject(ErrorHandlerService);
return {
fetch: () => http.get<T>(url).pipe(
catchError(err => {
errorHandler.handle(err);
return EMPTY;
})
)
};
}Модифікатори Впровадження
Angular пропонує низку модифікаторів для контролю процесу розв'язання залежностей.
@Component({
selector: 'app-example',
template: `<div>Example</div>`
})
export class ExampleComponent {
constructor(
// @Optional() - не генерує помилку при відсутності провайдера
@Optional() private optionalService: OptionalService | null,
// @Self() - шукає лише в поточному інжекторі
@Self() private selfService: SelfService,
// @SkipSelf() - пропускає поточний інжектор
@SkipSelf() private parentService: ParentService,
// @Host() - шукає до рівня хоста включно
@Host() private hostService: HostService
) {}
}
// Комбінації модифікаторів
@Component({
selector: 'app-combined',
template: `<div>Combined</div>`
})
export class CombinedComponent {
constructor(
@Optional() @SkipSelf() private parentConfig: ConfigService | null
) {
// Шукає в батьківському, але не генерує помилку при відсутності
}
}Multi Providers - Множинні Реалізації
Multi providers дозволяють реєструвати декілька значень під одним токеном.
// Визначення токена для інтерсепторів
export const HTTP_INTERCEPTORS_TOKEN = new InjectionToken<HttpInterceptor[]>(
'http.interceptors'
);
// Реєстрація декількох інтерсепторів
@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS_TOKEN,
useClass: AuthInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS_TOKEN,
useClass: LoggingInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS_TOKEN,
useClass: ErrorInterceptor,
multi: true
}
]
})
export class AppModule {}
// Використання всіх інтерсепторів
@Injectable()
export class HttpService {
constructor(
@Inject(HTTP_INTERCEPTORS_TOKEN)
private interceptors: HttpInterceptor[]
) {
console.log(`Loaded ${interceptors.length} interceptors`);
}
}Провайдери на Рівні Компонента
Реєстрація провайдерів на рівні компонента створює ізольовані екземпляри сервісів.
@Injectable()
export class FormStateService {
private formData = signal<Record<string, unknown>>({});
updateField(key: string, value: unknown) {
this.formData.update(data => ({ ...data, [key]: value }));
}
getFormData() {
return this.formData();
}
reset() {
this.formData.set({});
}
}
@Component({
selector: 'app-user-form',
providers: [FormStateService], // Новий екземпляр для кожної форми
template: `
<form>
<input (input)="onInput('name', $event)" />
<input (input)="onInput('email', $event)" />
</form>
`
})
export class UserFormComponent {
private formState = inject(FormStateService);
onInput(field: string, event: Event) {
const value = (event.target as HTMLInputElement).value;
this.formState.updateField(field, value);
}
}Тестування з Dependency Injection
Правильне використання DI значно спрощує тестування застосунків.
describe('UserService', () => {
let service: UserService;
let httpMock: jasmine.SpyObj<HttpClient>;
beforeEach(() => {
httpMock = jasmine.createSpyObj('HttpClient', ['get', 'post']);
TestBed.configureTestingModule({
providers: [
UserService,
{ provide: HttpClient, useValue: httpMock },
{ provide: API_BASE_URL, useValue: 'https://test.api.com' }
]
});
service = TestBed.inject(UserService);
});
it('should fetch users from correct URL', () => {
httpMock.get.and.returnValue(of([]));
service.getUsers();
expect(httpMock.get).toHaveBeenCalledWith(
'https://test.api.com/users'
);
});
});
// Тестування з overrideProvider
describe('FeatureComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [FeatureComponent]
}).overrideProvider(FeatureService, {
useValue: { getData: () => of(mockData) }
});
});
});Готовий до співбесід з Angular?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Поширені Питання на Співбесідах
Нижче наведено типові питання щодо DI, які зустрічаються на технічних співбесідах на позиції Angular developer.
Питання 1: Яка різниця між providedIn: 'root' та реєстрацією в NgModule?
providedIn: 'root' реєструє сервіс як singleton у root інжекторі та підтримує tree-shaking - сервіс буде видалено з бандлу, якщо він не використовується. Реєстрація в NgModule не підтримує автоматичний tree-shaking і вимагає явного імпорту модуля.
Питання 2: Коли використовувати @Self() vs @SkipSelf()?
@Self() обмежує пошук провайдера поточним element інжектором - корисно, коли компонент потребує власного екземпляра сервісу. @SkipSelf() пропускає поточний інжектор і починає пошук з батьківського - використовується, коли компоненту потрібен доступ до екземпляра з вищого рівня ієрархії.
Питання 3: Як реалізувати патерн Strategy за допомогою DI?
// Інтерфейс стратегії
abstract class PaymentStrategy {
abstract process(amount: number): Observable<PaymentResult>;
}
// Реалізації
@Injectable()
export class CreditCardStrategy extends PaymentStrategy {
process(amount: number) {
return of({ success: true, method: 'credit_card' });
}
}
@Injectable()
export class PayPalStrategy extends PaymentStrategy {
process(amount: number) {
return of({ success: true, method: 'paypal' });
}
}
// Динамічний вибір стратегії
export const PAYMENT_STRATEGY = new InjectionToken<PaymentStrategy>(
'payment.strategy'
);
// Конфігурація в модулі
@NgModule({
providers: [
{
provide: PAYMENT_STRATEGY,
useFactory: (config: AppConfig) => {
return config.defaultPayment === 'paypal'
? new PayPalStrategy()
: new CreditCardStrategy();
},
deps: [APP_CONFIG_TOKEN]
}
]
})
export class PaymentModule {}Питання 4: Як уникнути circular dependency в DI?
Circular dependencies можна вирішити через:
- Використання
forwardRef()для відкладеного розв'язання посилання - Рефакторинг до спільного посередницького сервісу
- Застосування патерну mediator
- Використання InjectionToken з factory
// forwardRef для циклічних залежностей
@Injectable()
export class ServiceA {
constructor(
@Inject(forwardRef(() => ServiceB))
private serviceB: ServiceB
) {}
}Найкращі Практики
- Віддавати перевагу providedIn: 'root' для синглтонів застосунку - підтримує tree-shaking
- Використовувати InjectionToken для значень, що не є класами
- Застосовувати функцію inject() замість впровадження через конструктор у нових проєктах
- Уникати провайдерів у компонентах, якщо не потрібні ізольовані екземпляри
- Документувати складні factory коментарями, що пояснюють логіку створення
- Тестувати конфігурацію DI через unit-тести, що перевіряють правильність впровадження
Висновок
Просунуті механізми Dependency Injection в Angular 19 надають потужні інструменти для управління залежностями в застосунках. Розуміння ієрархії інжекторів, різноманіття провайдерів та сучасних патернів з функцією inject() є критично важливим для побудови масштабованих та тестованих застосунків.
Для розробників, що готуються до технічних співбесід, особливо важливим є практичне розуміння відмінностей між типами провайдерів, здатність пояснити процес resolution та знання найкращих практик, що застосовуються у продакшн-застосунках Angular.
Чи знайдеш ти помилку в Angular?
Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Автор:
Anthony Fillion-MailletЗасновник SharpSkill
Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.
Оновлено 13 вересня 2026 р.
Теги
Поділитися
Пов'язані статті

Синтаксис Control Flow в Angular 2026: @if, @for, @switch та питання на співбесідах
Повний посібник з блокового синтаксису керування потоком в Angular. Вивчіть @if, @for, @switch з практичними прикладами коду та підготуйтесь до технічних співбесід.

NgRx Signal Store vs Класичний NgRx у 2026: Який Обрати?
Комплексне порівняння NgRx Signal Store та класичного NgRx для управління станом в Angular. Практичні приклади коду та рекомендації на 2026 рік.

Питання співбесіди Angular 19: Signals, SSR і обов'язкові концепції
Найпоширеніші питання співбесіди з Angular 19: Signals, інкрементальна гідратація, zoneless change detection і нові реактивні API з прикладами коду та очікуваними відповідями.