2026'da İleri Düzey Angular Dependency Injection: Providers, Tokens ve Mülakat Soruları

Angular'da ileri düzey Dependency Injection mekanizmalarına kapsamlı bir bakış. Providers, InjectionToken, injector hiyerarşisi ve teknik mülakatlarda karşılaşılan sorular.

Advanced Angular Dependency Injection illustration

Dependency Injection (DI), Angular mimarisinin temel taşını oluşturur ve framework'ün en önemli tasarım kalıplarından biridir. Temel DI kavramları nispeten kolay anlaşılırken, ileri düzey teknikler injector'ların, provider'ların ve token'ların çalışma mekanizmalarının derinlemesine kavranmasını gerektirir.

Bu makalede, Angular 19'daki DI sisteminin ileri düzey yönleri ele alınmaktadır. Bu konular hem günlük geliştirme pratikleri hem de senior ve lead developer pozisyonları için yapılan teknik mülakatlar açısından kritik öneme sahiptir.

Angular 19, DI sisteminde önemli iyileştirmeler getirmiştir. Bunlar arasında signals ile daha iyi entegrasyon ve provider yapılandırması için yeni olanaklar yer almaktadır. Bu yeniliklerin bilinmesi teknik mülakatlarda avantaj sağlayabilir.

Angular'da Injector Hiyerarşisi

Angular'daki DI sistemi, hiyerarşik bir injector yapısına dayanmaktadır. Bu hiyerarşinin anlaşılması, uygulamadaki bağımlılıkların etkin yönetimi için temel bir gereksinimdir.

Angular üç ana injector seviyesi tanımlar:

  1. Platform Injector - En üst seviye, sayfadaki tüm Angular uygulamaları arasında paylaşılır
  2. Root Injector - Uygulama seviyesi, bootstrap sırasında oluşturulur
  3. Element Injector - Bileşen veya direktif seviyesi
typescript
// Örnek injector hiyerarşisi
@Injectable({
  providedIn: 'root' // Root Injector'a kayıt
})
export class GlobalConfigService {
  readonly apiUrl = 'https://api.example.com';
}

@Injectable()
export class FeatureService {
  // Modül veya bileşen seviyesinde kaydedilebilir
}

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

Bağımlılık çözümleme süreci en yakın injector'dan başlar ve uygun provider bulunana veya hata fırlatılana kadar hiyerarşide yukarı doğru ilerler.

Provider Türleri ve Kullanım Alanları

Angular, her biri benzersiz kullanım senaryolarına sahip çeşitli provider yapılandırma yöntemleri sunar.

useClass - Temel Uygulama

typescript
// Temel useClass kullanımı
const providers = [
  { provide: LoggerService, useClass: LoggerService },
  // Kısa form:
  LoggerService
];

// Uygulama değiştirme
@NgModule({
  providers: [
    { provide: LoggerService, useClass: AdvancedLoggerService }
  ]
})
export class AppModule {}

useValue - Statik Değerler

typescript
// Uygulama yapılandırması değer olarak
export interface AppConfig {
  apiUrl: string;
  debugMode: boolean;
  maxRetries: number;
}

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

// Yapılandırma için token
export const APP_CONFIG_TOKEN = new InjectionToken<AppConfig>('app.config');

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

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

useFactory - Dinamik Instance Oluşturma

typescript
// Bağımlılıklı 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 - Servis Takma Adları

typescript
// Mevcut servis için takma ad oluşturma
@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 - Güvenli Tip Tanımlama

InjectionToken, sınıf olmayan değerler için güvenli token tanımlama yöntemi sağlar.

typescript
// Varsayılan değerli token tanımları
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]
    ])
  }
);

// Diğer servislere bağımlı token
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() Fonksiyonu ve Modern Kalıplar

Angular 19, bağımlılık enjeksiyonu için tercih edilen yöntem olarak inject() fonksiyonunu öne çıkarmaktadır.

typescript
// Constructor üzerinden geleneksel enjeksiyon
@Injectable({ providedIn: 'root' })
export class TraditionalService {
  constructor(
    private http: HttpClient,
    private logger: LoggerService
  ) {}
}

// inject() ile modern yaklaşım
@Injectable({ providedIn: 'root' })
export class ModernService {
  private http = inject(HttpClient);
  private logger = inject(LoggerService);
  
  // Koşullu enjeksiyon
  private optionalService = inject(OptionalService, { optional: true });
}

// Yardımcı fonksiyonlarda 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;
      })
    )
  };
}

Enjeksiyon Değiştiricileri

Angular, bağımlılık çözümleme sürecini kontrol eden çeşitli değiştiriciler sunar.

typescript
@Component({
  selector: 'app-example',
  template: `<div>Example</div>`
})
export class ExampleComponent {
  constructor(
    // @Optional() - provider yoksa hata fırlatmaz
    @Optional() private optionalService: OptionalService | null,
    
    // @Self() - sadece mevcut injector'da arar
    @Self() private selfService: SelfService,
    
    // @SkipSelf() - mevcut injector'ı atlar
    @SkipSelf() private parentService: ParentService,
    
    // @Host() - host seviyesine kadar arar
    @Host() private hostService: HostService
  ) {}
}

// Değiştirici kombinasyonları
@Component({
  selector: 'app-combined',
  template: `<div>Combined</div>`
})
export class CombinedComponent {
  constructor(
    @Optional() @SkipSelf() private parentConfig: ConfigService | null
  ) {
    // Üst seviyede arar ama bulamazsa hata vermez
  }
}

Multi Providers - Çoklu Uygulamalar

Multi provider'lar, tek bir token altında birden fazla değer kaydedilmesine olanak tanır.

typescript
// Interceptor'lar için token tanımı
export const HTTP_INTERCEPTORS_TOKEN = new InjectionToken<HttpInterceptor[]>(
  'http.interceptors'
);

// Birden fazla interceptor kaydı
@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 {}

// Tüm interceptor'ların kullanımı
@Injectable()
export class HttpService {
  constructor(
    @Inject(HTTP_INTERCEPTORS_TOKEN) 
    private interceptors: HttpInterceptor[]
  ) {
    console.log(`Loaded ${interceptors.length} interceptors`);
  }
}

Bileşen Seviyesinde Provider'lar

Bileşen seviyesinde provider kaydı, izole servis instance'ları oluşturur.

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], // Her form için yeni instance
  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 ile Test Yazma

DI'nin doğru kullanımı uygulama testlerini önemli ölçüde kolaylaştırır.

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

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

Angular mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Sık Sorulan Mülakat Soruları

Aşağıda Angular developer pozisyonları için yapılan teknik mülakatlarda karşılaşılan tipik DI soruları yer almaktadır.

Soru 1: providedIn: 'root' ile NgModule'de kayıt arasındaki fark nedir?

providedIn: 'root' servisi root injector'da singleton olarak kaydeder ve tree-shaking'i destekler - servis kullanılmıyorsa bundle'dan kaldırılır. NgModule'de kayıt otomatik tree-shaking'i desteklemez ve modülün açıkça import edilmesini gerektirir.

Soru 2: @Self() ve @SkipSelf() ne zaman kullanılır?

@Self() provider aramasını mevcut element injector ile sınırlar - bileşen kendi servis instance'ına ihtiyaç duyduğunda kullanışlıdır. @SkipSelf() mevcut injector'ı atlar ve aramaya üst seviyeden başlar - bileşen hiyerarşide daha yukarıdaki bir instance'a erişmesi gerektiğinde kullanılır.

Soru 3: DI kullanarak Strategy pattern nasıl uygulanır?

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

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

// Dinamik strateji seçimi
export const PAYMENT_STRATEGY = new InjectionToken<PaymentStrategy>(
  'payment.strategy'
);

// Modülde yapılandırma
@NgModule({
  providers: [
    {
      provide: PAYMENT_STRATEGY,
      useFactory: (config: AppConfig) => {
        return config.defaultPayment === 'paypal'
          ? new PayPalStrategy()
          : new CreditCardStrategy();
      },
      deps: [APP_CONFIG_TOKEN]
    }
  ]
})
export class PaymentModule {}

Soru 4: DI'da circular dependency nasıl önlenir?

Circular dependency'ler şu yöntemlerle çözülebilir:

  • Referansın gecikmeli çözümlenmesi için forwardRef() kullanımı
  • Ortak bir aracı servise refactoring
  • Mediator pattern uygulaması
  • Factory ile InjectionToken kullanımı
typescript
// Döngüsel bağımlılıklar için forwardRef
@Injectable()
export class ServiceA {
  constructor(
    @Inject(forwardRef(() => ServiceB)) 
    private serviceB: ServiceB
  ) {}
}

En İyi Uygulamalar

  1. Uygulama singleton'ları için providedIn: 'root' tercih edilmeli - tree-shaking'i destekler
  2. Sınıf olmayan değerler için InjectionToken kullanılmalı
  3. Yeni projelerde constructor enjeksiyonu yerine inject() fonksiyonu tercih edilmeli
  4. İzole instance'lar gerekmedikçe bileşen provider'larından kaçınılmalı
  5. Karmaşık factory'ler oluşturma mantığını açıklayan yorumlarla belgelenmeli
  6. DI yapılandırması doğru enjeksiyonu doğrulayan unit testlerle test edilmeli

Sonuç

Angular 19'daki ileri düzey Dependency Injection mekanizmaları, uygulamalarda bağımlılık yönetimi için güçlü araçlar sunmaktadır. Injector hiyerarşisinin, çeşitli provider türlerinin ve inject() fonksiyonu ile modern kalıpların anlaşılması, ölçeklenebilir ve test edilebilir uygulamalar geliştirmek için kritik öneme sahiptir.

Teknik mülakatlara hazırlanan geliştiriciler için provider türleri arasındaki farkların pratik olarak anlaşılması, resolution sürecinin açıklanabilmesi ve üretim Angular uygulamalarında kullanılan en iyi uygulamaların bilinmesi özellikle önemlidir.

Günün meydan okuması

Angular kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

13 Eylül 2026 tarihinde güncellendi

Etiketler

#angular
#dependency-injection
#typescript
#interview

Paylaş

İlgili makaleler