Zaawansowany Dependency Injection w Angular 2026: Providers, Tokens i Pytania Rekrutacyjne

Kompleksowy przewodnik po zaawansowanych mechanizmach Dependency Injection w Angular. Providers, InjectionToken, hierarchia injectorów oraz najczęstsze pytania na rozmowach kwalifikacyjnych.

Advanced Angular Dependency Injection illustration

Dependency Injection (DI) stanowi fundament architektury Angular i jest jednym z najważniejszych wzorców projektowych wykorzystywanych w tym frameworku. Podczas gdy podstawowe koncepcje DI są stosunkowo proste do opanowania, zaawansowane techniki wymagają głębszego zrozumienia mechanizmów działania injectorów, providerów oraz tokenów.

W tym artykule omówione zostaną zaawansowane aspekty systemu DI w Angular 19, które są kluczowe zarówno dla codziennej pracy deweloperskiej, jak i podczas rozmów kwalifikacyjnych na stanowiska senior i lead developer.

Warto pamiętać, że Angular 19 wprowadził znaczące usprawnienia w systemie DI, w tym lepszą integrację z sygnałami oraz nowe możliwości konfiguracji providerów. Znajomość tych nowości może być kluczowa podczas rozmów rekrutacyjnych.

Hierarchia Injectorów w Angular

System DI w Angular opiera się na hierarchicznej strukturze injectorów. Zrozumienie tej hierarchii jest fundamentalne dla efektywnego zarządzania zależnościami w aplikacji.

Angular definiuje trzy główne poziomy injectorów:

  1. Platform Injector - najwyższy poziom, współdzielony między wszystkimi aplikacjami Angular na stronie
  2. Root Injector - poziom aplikacji, tworzony podczas bootstrapu
  3. Element Injector - poziom komponentu lub dyrektywy
typescript
// Przykład hierarchii injectorów
@Injectable({
  providedIn: 'root' // Rejestracja w Root Injector
})
export class GlobalConfigService {
  readonly apiUrl = 'https://api.example.com';
}

@Injectable()
export class FeatureService {
  // Może być zarejestrowany na poziomie modułu lub komponentu
}

@Component({
  selector: 'app-feature',
  providers: [FeatureService], // Element Injector
  template: `<div>Feature Component</div>`
})
export class FeatureComponent {
  constructor(
    private globalConfig: GlobalConfigService,
    private featureService: FeatureService
  ) {}
}

Proces rozwiązywania zależności rozpoczyna się od najbliższego injektora i przemieszcza się w górę hierarchii aż do znalezienia odpowiedniego providera lub zgłoszenia błędu.

Typy Providerów i ich Zastosowania

Angular oferuje różnorodne sposoby konfiguracji providerów, każdy z unikalnymi przypadkami użycia.

useClass - Podstawowa Implementacja

typescript
// Podstawowe użycie useClass
const providers = [
  { provide: LoggerService, useClass: LoggerService },
  // Skrócona forma:
  LoggerService
];

// Podmiana implementacji
@NgModule({
  providers: [
    { provide: LoggerService, useClass: AdvancedLoggerService }
  ]
})
export class AppModule {}

useValue - Statyczne Wartości

typescript
// Konfiguracja aplikacji jako wartość
export interface AppConfig {
  apiUrl: string;
  debugMode: boolean;
  maxRetries: number;
}

export const APP_CONFIG: AppConfig = {
  apiUrl: 'https://api.example.com',
  debugMode: false,
  maxRetries: 3
};

// Token dla konfiguracji
export const APP_CONFIG_TOKEN = new InjectionToken<AppConfig>('app.config');

// Rejestracja
@NgModule({
  providers: [
    { provide: APP_CONFIG_TOKEN, useValue: APP_CONFIG }
  ]
})
export class AppModule {}

// Użycie
@Injectable({ providedIn: 'root' })
export class ApiService {
  constructor(@Inject(APP_CONFIG_TOKEN) private config: AppConfig) {
    console.log(this.config.apiUrl);
  }
}

useFactory - Dynamiczne Tworzenie Instancji

typescript
// Factory z zależnościami
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 - Aliasy Serwisów

typescript
// Tworzenie aliasu dla istniejącego serwisu
@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 - Bezpieczne Typowanie

InjectionToken zapewnia bezpieczny sposób definiowania tokenów dla wartości nie będących klasami.

typescript
// Definicja tokenów z domyślnymi wartościami
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]
    ])
  }
);

// Token z zależnością od innych serwisów
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
      };
    }
  }
);

Funkcja inject() i Nowoczesne Wzorce

Angular 19 promuje użycie funkcji inject() jako preferowanej metody wstrzykiwania zależności.

typescript
// Tradycyjne wstrzykiwanie przez konstruktor
@Injectable({ providedIn: 'root' })
export class TraditionalService {
  constructor(
    private http: HttpClient,
    private logger: LoggerService
  ) {}
}

// Nowoczesne podejście z inject()
@Injectable({ providedIn: 'root' })
export class ModernService {
  private http = inject(HttpClient);
  private logger = inject(LoggerService);
  
  // Warunkowe wstrzykiwanie
  private optionalService = inject(OptionalService, { optional: true });
}

// inject() w funkcjach pomocniczych
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;
      })
    )
  };
}

Modyfikatory Wstrzykiwania

Angular oferuje szereg modyfikatorów kontrolujących proces rozwiązywania zależności.

typescript
@Component({
  selector: 'app-example',
  template: `<div>Example</div>`
})
export class ExampleComponent {
  constructor(
    // @Optional() - nie zgłasza błędu gdy brak providera
    @Optional() private optionalService: OptionalService | null,
    
    // @Self() - szuka tylko w bieżącym injektorze
    @Self() private selfService: SelfService,
    
    // @SkipSelf() - pomija bieżący injektor
    @SkipSelf() private parentService: ParentService,
    
    // @Host() - szuka do poziomu hosta włącznie
    @Host() private hostService: HostService
  ) {}
}

// Kombinacje modyfikatorów
@Component({
  selector: 'app-combined',
  template: `<div>Combined</div>`
})
export class CombinedComponent {
  constructor(
    @Optional() @SkipSelf() private parentConfig: ConfigService | null
  ) {
    // Szuka w rodzicu, ale nie zgłasza błędu gdy brak
  }
}

Multi Providers - Wiele Implementacji

Multi providers umożliwiają rejestrację wielu wartości pod jednym tokenem.

typescript
// Definicja tokenu dla interceptorów
export const HTTP_INTERCEPTORS_TOKEN = new InjectionToken<HttpInterceptor[]>(
  'http.interceptors'
);

// Rejestracja wielu interceptorów
@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 {}

// Użycie wszystkich interceptorów
@Injectable()
export class HttpService {
  constructor(
    @Inject(HTTP_INTERCEPTORS_TOKEN) 
    private interceptors: HttpInterceptor[]
  ) {
    console.log(`Loaded ${interceptors.length} interceptors`);
  }
}

Providery na Poziomie Komponentu

Rejestracja providerów na poziomie komponentu tworzy izolowane instancje serwisów.

typescript
@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], // Nowa instancja dla każdego formularza
  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);
  }
}

Testowanie z Dependency Injection

Prawidłowe wykorzystanie DI znacząco ułatwia testowanie aplikacji.

typescript
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'
    );
  });
});

// Testowanie z overrideProvider
describe('FeatureComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [FeatureComponent]
    }).overrideProvider(FeatureService, {
      useValue: { getData: () => of(mockData) }
    });
  });
});

Gotowy na rozmowy o Angular?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Częste Pytania Rekrutacyjne

Poniżej przedstawiono typowe pytania dotyczące DI, które pojawiają się podczas rozmów kwalifikacyjnych na stanowiska Angular developer.

Pytanie 1: Jaka jest różnica między providedIn: 'root' a rejestracją w NgModule?

providedIn: 'root' rejestruje serwis jako singleton w root injektorze i umożliwia tree-shaking - serwis zostanie usunięty z bundla jeśli nie jest używany. Rejestracja w NgModule nie wspiera automatycznego tree-shaking i wymaga jawnego importu modułu.

Pytanie 2: Kiedy użyć @Self() vs @SkipSelf()?

@Self() ogranicza wyszukiwanie providera do bieżącego element injektora - przydatne gdy komponent wymaga własnej instancji serwisu. @SkipSelf() pomija bieżący injektor i rozpoczyna wyszukiwanie od rodzica - używane gdy komponent potrzebuje dostępu do instancji z wyższego poziomu hierarchii.

Pytanie 3: Jak zaimplementować wzorzec Strategy używając DI?

typescript
// Interface strategii
abstract class PaymentStrategy {
  abstract process(amount: number): Observable<PaymentResult>;
}

// Implementacje
@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' });
  }
}

// Dynamiczny wybór strategii
export const PAYMENT_STRATEGY = new InjectionToken<PaymentStrategy>(
  'payment.strategy'
);

// Konfiguracja w module
@NgModule({
  providers: [
    {
      provide: PAYMENT_STRATEGY,
      useFactory: (config: AppConfig) => {
        return config.defaultPayment === 'paypal'
          ? new PayPalStrategy()
          : new CreditCardStrategy();
      },
      deps: [APP_CONFIG_TOKEN]
    }
  ]
})
export class PaymentModule {}

Pytanie 4: Jak uniknąć circular dependency w DI?

Circular dependencies można rozwiązać poprzez:

  • Użycie forwardRef() do opóźnionego rozwiązania referencji
  • Refaktoryzację do wspólnego serwisu pośredniczącego
  • Zastosowanie wzorca mediator
  • Wykorzystanie InjectionToken z factory
typescript
// forwardRef dla cyklicznych zależności
@Injectable()
export class ServiceA {
  constructor(
    @Inject(forwardRef(() => ServiceB)) 
    private serviceB: ServiceB
  ) {}
}

Najlepsze Praktyki

  1. Preferuj providedIn: 'root' dla singletonów aplikacji - wspiera tree-shaking
  2. Używaj InjectionToken dla wartości nie będących klasami
  3. Stosuj funkcję inject() zamiast wstrzykiwania przez konstruktor w nowych projektach
  4. Unikaj providerów w komponentach chyba że potrzebujesz izolowanych instancji
  5. Dokumentuj złożone factory komentarzami wyjaśniającymi logikę tworzenia
  6. Testuj konfigurację DI poprzez unit testy sprawdzające prawidłowe wstrzykiwanie

Podsumowanie

Zaawansowane mechanizmy Dependency Injection w Angular 19 oferują potężne narzędzia do zarządzania zależnościami w aplikacjach. Zrozumienie hierarchii injektorów, różnorodności providerów oraz nowoczesnych wzorców z funkcją inject() jest kluczowe dla budowania skalowalnych i testowalnych aplikacji.

Dla osób przygotowujących się do rozmów rekrutacyjnych, szczególnie istotne jest praktyczne zrozumienie różnic między typami providerów, umiejętność wyjaśnienia procesu resolution oraz znajomość najlepszych praktyk stosowanych w produkcyjnych aplikacjach Angular.

Wyzwanie dnia

Znajdziesz błąd w Angular?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 13 września 2026

Tagi

#angular
#dependency-injection
#typescript
#interview

Udostępnij

Powiązane artykuły