Bağımlılık Enjeksiyonu

Servis Kaydı

ServiceCollection kayıt biçimleri, başlangıçta oluşturulan singleton'lar, @Injectable ile otomatik kayıt ve seçenekleri.

Servis kaydı

Servisler Application.CreateBuilder()'ın döndürdüğü kurucunun services koleksiyonuna (ServiceCollection), build()'den önce kaydedilir. Her kayıt bir token'ı (çoğunlukla sınıfın kendisi; dizge ya da sembol de olabilir) bir uygulamaya ve bir yaşam süresine bağlar; yaşam süreleri Yaşam Süreleri ve Kapsamlar sayfasındadır:

const builder = Application.CreateBuilder();

builder.services.addSingleton(NotesService, NotesService);   // uygulama boyunca tek örnek
builder.services.addScoped(NoteDraft, NoteDraft);            // gezinme başına bir örnek
builder.services.addTransient(SearchIndex, SearchIndex);     // her çözümlemede yeni örnek

const app = builder.build();
const builder = Application.CreateBuilder();

builder.services.addSingleton(NotesService, NotesService);   // uygulama boyunca tek örnek
builder.services.addScoped(NoteDraft, NoteDraft);            // gezinme başına bir örnek
builder.services.addTransient(SearchIndex, SearchIndex);     // her çözümlemede yeni örnek

const app = builder.build();

Kayıt biçimleri

add*(token, impl) metotlarında impl şunlardan biri olabilir:

// 1) Sınıf (useClass)
builder.services.addSingleton(NotesService, NotesService);

// 2) Hazır değer (useValue): sınıf olmayan bir nesne olduğu gibi kaydedilir
builder.services.addSingleton("config", { apiUrl: "/api/notes" });

// 3) Fabrika / descriptor nesnesi: `deps` verilirse fabrika (…bağımlılıklar, sağlayıcı) ile çağrılır
builder.services.addSingleton(SearchIndex, {
  useFactory: (notes: NotesService) => new SearchIndex(notes),
  deps: [NotesService],
});

// deps'siz fabrika yalnızca sağlayıcıyı alır
builder.services.addSingleton(SearchIndex, { useFactory: (sp) => new SearchIndex(sp.get(NotesService)) });

// 4) Async değer (Promise): async fabrikaya sarılır; `get` değil `getAsync` ile çözümlenir
builder.services.addSingleton(SyncService, import("./services/sync").then((m) => new m.SyncService()));
const sync = await app.provider.getAsync(SyncService);
// 1) Sınıf (useClass)
builder.services.addSingleton(NotesService, NotesService);

// 2) Hazır değer (useValue): sınıf olmayan bir nesne olduğu gibi kaydedilir
builder.services.addSingleton("config", { apiUrl: "/api/notes" });

// 3) Fabrika / descriptor nesnesi: `deps` verilirse fabrika (…bağımlılıklar, sağlayıcı) ile çağrılır
builder.services.addSingleton(SearchIndex, {
  useFactory: (notes) => new SearchIndex(notes),
  deps: [NotesService],
});

// deps'siz fabrika yalnızca sağlayıcıyı alır
builder.services.addSingleton(SearchIndex, { useFactory: (sp) => new SearchIndex(sp.get(NotesService)) });

// 4) Async değer (Promise): async fabrikaya sarılır; `get` değil `getAsync` ile çözümlenir
builder.services.addSingleton(SyncService, import("./services/sync").then((m) => new m.SyncService()));
const sync = await app.provider.getAsync(SyncService);

deps dizisi hem sınıf hem fabrika kayıtlarında geçerlidir: sıralı olarak çözümlenir ve yapıcıya/fabrikaya aynı sırada geçirilir; fabrikada son parametre her zaman ServiceProvider'dır.

Sağlayıcı yalnız sınıf kaydını (useClass) new ile kurar. useValue ile verilen değer ve fabrikanın döndürdüğü değer olduğu gibi verilir; get ve getAsync aynı değeri döndürür. Bir fonksiyonu ya da sınıfın kendisini servis olarak vermek için { useValue: fn } veya onu döndüren bir fabrika kullanın; örnek isteyen fabrika new'i kendisi yazar.

add*(token, impl)'e doğrudan verilen bir fonksiyon sınıf olarak kurulur. Kurulamayan bir fonksiyon (ok fonksiyonu, async fonksiyon, nesne metodu) TypeScript'te derleme hatası verir, çalışma zamanında da kayıt satırında MJX413 fırlatır:

builder.services.addSingleton('formatter', (x: number) => x.toFixed(2));             // hata: MJX413
builder.services.addSingleton('formatter', { useValue: (x: number) => x.toFixed(2) }); // fonksiyonun kendisi
builder.services.addSingleton(SearchIndex, { useFactory: (sp) => new SearchIndex(sp.get(NotesService)) }); // fabrika
builder.services.addSingleton('formatter', (x) => x.toFixed(2));             // hata: MJX413
builder.services.addSingleton('formatter', { useValue: (x) => x.toFixed(2) }); // fonksiyonun kendisi
builder.services.addSingleton(SearchIndex, { useFactory: (sp) => new SearchIndex(sp.get(NotesService)) }); // fabrika

ServiceCollection metotları:

Metot Açıklama
addSingleton/addScoped/addTransient(token, impl) Kayıt.
tryAddSingleton/tryAddScoped/tryAddTransient(token, impl) Yalnızca kayıtlı değilse ekler; boolean döner.
replace(token, impl, lifetime?) Var olan kaydı değiştirir.
remove(token) / has(token) Kaldır / kontrol et.
reset() Tümünü temizle.
buildServiceProvider() ServiceProvider üretir (build() bunu otomatik yapar).

Başlangıçta oluşturulan singleton'lar

Bir singleton'ın hiçbir yerden istenmeden uygulama başlarken oluşturulması için kayda startup: true verilir. Kurucusu kendi başına iş yapan servisler (belgeye öznitelik yazan, dinleyici takan, bağlantı açan) böyle kaydedilir. Örnekteki servis kurulurken bir effect açar ve seçili temayı <html> elemanına yazar; hiçbir bileşen onu istemese de çalışması gerekir:

@Injectable({ lifetime: "singleton", startup: true })
export class SettingsService {
  readonly state = reactive({ theme: "system" as Theme });

  constructor() {
    effect(() => {
      const root = document.documentElement;
      if (this.state.theme === "system") delete root.dataset.theme;
      else root.dataset.theme = this.state.theme;
    });
  }
}
@Injectable({ lifetime: "singleton", startup: true })
export class SettingsService {
  state = reactive({ theme: "system" });

  constructor() {
    effect(() => {
      const root = document.documentElement;
      if (this.state.theme === "system") delete root.dataset.theme;
      else root.dataset.theme = this.state.theme;
    });
  }
}

Açık kayıtta startup nesne biçiminde verilir; useClass ve useFactory ile çalışır, tryAddSingleton aynı biçimi alır:

builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
builder.services.addSingleton(SearchIndex, {
  useFactory: (notes: NotesService) => new SearchIndex(notes),
  deps: [NotesService],
  startup: true,
});
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
builder.services.addSingleton(SearchIndex, {
  useFactory: (notes) => new SearchIndex(notes),
  deps: [NotesService],
  startup: true,
});

Dekoratörlerle otomatik kayıt

@Injectable, bir sınıfı otomatik kayıt için işaretler; açık bir add* satırı gerekmez. Uygulama build edilirken (builder.build() içinde autoRegisterInjectables çağrılır) işaretli sınıflar seçenekleriyle kaydedilir.

Modülü build()'den sonra yüklenen sınıflar da (yalnızca tembel bir sayfanın içe aktardığı servisler) kaydedilir: tanımlayıcı bulunamadığında kayıt ilk çözümleme anında yapılır. Açık kayıt (addSingleton/addScoped/addTransient) her zaman dekoratörü ezer, dekoratör açık kaydı ezemez.

import { Injectable, inject, reactive } from "@motifx/core";

@Injectable({ lifetime: "singleton" })
export class NotesService {
  readonly state = reactive({ items: [] as Note[] });
}

@Injectable()   // varsayılan lifetime: 'transient'
export class SearchIndex {
  private notes = inject(NotesService);

  count() {
    return this.notes.state.items.length;
  }
}
import { Injectable, inject, reactive } from "@motifx/core";

@Injectable({ lifetime: "singleton" })
export class NotesService {
  state = reactive({ items: [] });
}

@Injectable()   // varsayılan lifetime: 'transient'
export class SearchIndex {
  notes = inject(NotesService);

  count() {
    return this.notes.state.items.length;
  }
}

@Injectable standart (TC39) bir sınıf dekoratörüdür; tsconfig.json'da ya da Vite'ın esbuild ayarında ek bir ayar gerektirmez. @motifx/compiler dekoratörleri .tsx dosyalarında olduğu gibi .ts ve .js dosyalarında da derler; vite dev ve vite build aynı sonucu verir. En yakın tsconfig.json'unda experimentalDecorators: true olan dosyalar TypeScript'in eski dekoratör dönüşümüne bırakılır; @Injectable o biçimde de çalışır.

@Injectable seçenekleri

Seçenek Anlam
lifetime 'singleton', 'scoped' ya da 'transient'; verilmezse 'transient'.
deps Yapıcı parametreleri olarak sırayla çözülen token'lar (bkz. deps).
startup Yalnız singleton'da: servis run() içinde, istenmeden oluşturulur (bkz. Başlangıçta oluşturulan singleton'lar).

Sonraki adım

Bir servisin ne kadar yaşadığını öğrenmek için Yaşam Süreleri ve Kapsamlar bölümüne geçin.