Detaylı Rehberler
Bağımlılık Enjeksiyonu

Hiyerarşik injector'lar

Bu kılavuz, Angular'ın hiyerarşik bağımlılık enjeksiyonu sistemi hakkında çözümleme kuralları, değiştiriciler ve gelişmiş desenler dahil olmak üzere derinlemesine bilgi sağlar.

NOTE: Enjektör hiyerarşisi ve sağlayıcı kapsamı hakkında temel kavramlar için bağımlılık sağlayıcılarını tanımlama kılavuzuna bakın.

Injector hiyerarşi türleri

Angular'da iki enjektör hiyerarşisi vardır:

Injector hierarchies Details
EnvironmentInjector hierarchy Bu hiyerarşide @Service() veya ApplicationConfig içindeki providers dizisini kullanarak bir EnvironmentInjector yapılandırın.
ElementInjector hierarchy Her DOM elemanında örtük olarak oluşturulur. Bir ElementInjector, @Directive() veya @Component() üzerindeki providers özelliğinde yapılandırmadığınız sürece varsayılan olarak boştur.

NgModule Tabanlı Uygulamalar

NgModule tabanlı uygulamalar için, @NgModule() veya @Injectable() anotasyonunu kullanarak ModuleInjector hiyerarşisi ile bağımlılıklar sağlayabilirsiniz.

EnvironmentInjector

EnvironmentInjector iki şekilde yapılandırılabilir:

Tree-shaking ve @Service()

@Service() dekoratörünü kullanmak, ApplicationConfig providers dizisini kullanmaktan tercih edilir. @Service ile optimizasyon araçları, uygulamanızın kullanmadığı servisleri kaldıran tree-shaking işlemi yapabilir. Bu, daha küçük paket boyutlarıyla sonuçlanır.

Tree-shaking özellikle bir kütüphane için kullanışlıdır çünkü kütüphaneyi kullanan uygulama onu enjekte etme ihtiyacı duymayabilir.

EnvironmentInjector, ApplicationConfig.providers tarafından yapılandırılır.

Servisleri @Service() kullanarak aşağıdaki gibi sağlayın:

import {Service} from '@angular/core';

@Service() // <--bu service'i root EnvironmentInjector'da sağlar
export class ItemService {
  name = 'telephone';
}

@Service() veya @Injectable() dekoratörleri bir servis sınıfını tanımlar.

ModuleInjector

NgModule tabanlı uygulamalarda, ModuleInjector iki şekilde yapılandırılabilir:

ModuleInjector, @NgModule.providers ve NgModule.imports özelliği tarafından yapılandırılır. ModuleInjector, NgModule.imports özyinelemeli olarak takip edilerek ulaşılabilen tüm providers dizilerinin düzleştirilmiş halidir.

Tembel yüklenen diğer @NgModule'lar yüklendiğinde alt ModuleInjector hiyerarşileri oluşturulur.

Platform injector'ı

root'un üzerinde iki enjektör daha vardır, ek bir EnvironmentInjector ve NullInjector().

Angular'ın uygulamayı main.ts içinde nasıl başlattığını düşünün:

bootstrapApplication(App, appConfig);

bootstrapApplication() yöntemi, ApplicationConfig örneği tarafından yapılandırılan platform enjektörünün bir alt enjektörünü oluşturur. Bu, root EnvironmentInjector'dır.

platformBrowserDynamic() yöntemi, platforma özgü bağımlılıklar içeren bir PlatformModule tarafından yapılandırılmış bir enjektör oluşturur. Bu, birden fazla uygulamanın bir platform yapılandırmasını paylaşmasına olanak tanır. Örneğin, bir tarayıcının kaç uygulama çalıştırırsanız çalıştırın yalnızca bir URL çubuğu vardır. platformBrowser() fonksiyonunu kullanarak extraProviders sağlayarak platform seviyesinde ek platforma özgü sağlayıcılar yapılandırabilirsiniz.

Hiyerarşideki bir sonraki üst enjektör, ağacın tepesi olan NullInjector()'dır. Ağaçta NullInjector() içinde bir servis arayacak kadar yukarı çıktıysanız, @Optional() kullanmadığınız sürece bir hata alırsınız çünkü sonuçta her şey NullInjector()'da biter ve @Optional() durumunda null döndürür ya da bir hata fırlatır. @Optional() hakkında daha fazla bilgi için bu kılavuzun @Optional() bölümüne bakın.

Aşağıdaki diyagram, önceki paragrafların açıkladığı gibi root ModuleInjector ile üst enjektörleri arasındaki ilişkiyi temsil eder.

EnvironmentInjector
(configured by Angular)
has special things like DomSanitizer => providedIn 'platform'

root EnvironmentInjector
(configured by AppConfig)
has things for your app => bootstrapApplication(..., AppConfig)

NullInjector
always throws an error unless
you use @Optional()

root adı özel bir takma ad olsa da, diğer EnvironmentInjector hiyerarşilerinin takma adları yoktur. Router gibi dinamik olarak yüklenen bir bileşen oluşturulduğunda, alt EnvironmentInjector hiyerarşileri oluşturacak olan EnvironmentInjector hiyerarşileri oluşturma seçeneğiniz vardır.

Tüm istekler, bootstrapApplication() yöntemine iletilen ApplicationConfig örneği ile yapılandırdıysanız veya tüm sağlayıcıları kendi servislerinde root ile kaydettiyseniz, root enjektöre yönlendirilir.

@Injectable() vs. ApplicationConfig

bootstrapApplication'ın ApplicationConfig'inde uygulama çapında bir sağlayıcı yapılandırırsanız, @Injectable() meta verilerinde root için yapılandırılmış olanı geçersiz kılar. Bunu, birden fazla uygulamayla paylaşılan bir servisin varsayılan olmayan bir sağlayıcısını yapılandırmak için yapabilirsiniz.

İşte bileşen yönlendirici yapılandırmasının, ApplicationConfig'in providers listesinde sağlayıcısını listeleyerek varsayılan olmayan bir konum stratejisi içerdiği bir örnek.

providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}];

NgModule tabanlı uygulamalar için, uygulama çapında sağlayıcıları AppModule providers'da yapılandırın.

ElementInjector

Angular, her DOM elemanı için örtük olarak ElementInjector hiyerarşileri oluşturur.

@Component() dekoratöründe providers veya viewProviders özelliğini kullanarak bir servis sağlamak, bir ElementInjector yapılandırır. Örneğin, aşağıdaki TestComponent, servisi şu şekilde sağlayarak ElementInjector'ı yapılandırır:

@Component({
  /* … */
  providers: [{ provide: ItemService, useValue: { name: 'lamp' } }]
})
export class TestComponent

HELPFUL: EnvironmentInjector ağacı, ModuleInjector ve ElementInjector ağacı arasındaki ilişkiyi anlamak için çözümleme kuralları bölümüne bakın.

Bir bileşende servisler sağladığınızda, bu servis o bileşen örneğindeki ElementInjector aracılığıyla kullanılabilir. Çözümleme kuralları bölümünde açıklanan görünürlük kurallarına göre alt bileşen/direktiflerde de görünür olabilir.

Bileşen örneği yok edildiğinde, o servis örneği de yok edilir.

@Directive() and @Component()

Bir bileşen özel bir direktif türüdür, yani @Directive()'in bir providers özelliği olduğu gibi, @Component()'in de vardır. Bu, direktiflerin yanı sıra bileşenlerin de providers özelliğini kullanarak sağlayıcılar yapılandırabileceği anlamına gelir. providers özelliğini kullanarak bir bileşen veya direktif için bir sağlayıcı yapılandırdığınızda, bu sağlayıcı o bileşen veya direktifin ElementInjector'ına aittir. Aynı eleman üzerindeki bileşenler ve direktifler bir enjektörü paylaşır.

Çözümleme kuralları

Bir bileşen/direktif için bir token çözümlenirken, Angular bunu iki aşamada çözümler:

  1. ElementInjector hiyerarşisindeki üst elemanlara karşı.
  2. EnvironmentInjector hiyerarşisindeki üst elemanlara karşı.

Bir bileşen bir bağımlılık bildirdiğinde, Angular önce bu bağımlılığı kendi ElementInjector'ı ile karşılamaya çalışır. Bileşenin enjektöründe sağlayıcı yoksa, isteği üst bileşenin ElementInjector'ına iletir.

İstekler, Angular isteği işleyebilecek bir enjektör bulana veya üst ElementInjector hiyerarşileri tükenene kadar yönlendirilmeye devam eder.

Angular herhangi bir ElementInjector hiyerarşisinde sağlayıcıyı bulamazsa, isteğin kaynaklandığı elemana geri döner ve EnvironmentInjector hiyerarşisine bakar. Angular hala sağlayıcıyı bulamazsa, bir hata fırlatır.

Aynı DI token'ı için farklı seviyelerde bir sağlayıcı kaydettiyseniz, Angular'ın karşılaştığı ilki bağımlılığı çözmek için kullanılır. Örneğin, bir sağlayıcı servise ihtiyaç duyan bileşende yerel olarak kayıtlıysa, Angular aynı servisin başka bir sağlayıcısını aramaz.

HELPFUL: NgModule tabanlı uygulamalarda, Angular ElementInjector hiyerarşilerinde sağlayıcı bulamazsa ModuleInjector hiyerarşisinde arama yapacaktır.

Çözümleme değiştiricileri

Angular'ın çözümleme davranışı optional, self, skipSelf ve host ile değiştirilebilir. Her birini @angular/core'dan içe aktarın ve servisinizi enjekte ederken inject yapılandırmasında kullanın.

Değiştirici türleri

Çözümleme değiştiricileri üç kategoriye ayrılır:

  • Angular aradığınızı bulamazsa ne yapılacağı, yani optional
  • Aramaya nereden başlanacağı, yani skipSelf
  • Aramanın nerede duracağı, host ve self

Varsayılan olarak, Angular her zaman mevcut Injector'da başlar ve sonuna kadar aramaya devam eder. Değiştiriciler, başlangıç veya self konumunu ve bitiş konumunu değiştirmenize olanak tanır.

Ayrıca, şunlar hariç tüm değiştiricileri birleştirebilirsiniz:

  • host ve self
  • skipSelf ve self.

optional

optional, Angular'ın enjekte ettiğiniz bir servisi isteğe bağlı olarak değerlendirmesine olanak tanır. Bu şekilde, çalışma zamanında çözümlenemezse, Angular bir hata fırlatmak yerine servisi null olarak çözümler. Aşağıdaki örnekte, OptionalService servisi serviste, ApplicationConfig'de, @NgModule()'da veya bileşen sınıfında sağlanmamıştır, bu nedenle uygulamada hiçbir yerde kullanılamaz.

src/app/optional/optional.ts

export class Optional {
  public optional? = inject(OptionalService, {optional: true});
}

self

Angular'ın yalnızca mevcut bileşen veya direktifin ElementInjector'ına bakması için self kullanın.

self için iyi bir kullanım durumu, bir servisi yalnızca mevcut ana elemanda mevcutsa enjekte etmektir. Bu durumda hataları önlemek için self'i optional ile birleştirin.

Örneğin, aşağıdaki SelfNoData'da, enjekte edilen LeafService'e bir özellik olarak dikkat edin.

self-no-data.ts

@Component({
  selector: 'app-self-no-data',
  templateUrl: './self-no-data.html',
  styleUrls: ['./self-no-data.css'],
})
export class SelfNoData {
  public leaf = inject(LeafService, {optional: true, self: true});
}

Bu örnekte, bir üst sağlayıcı vardır ve servisi enjekte etmek değeri döndürür, ancak servisi self ve optional ile enjekte etmek null döndürür çünkü self enjektöre mevcut ana elemanda aramayı durdurmasını söyler.

Başka bir örnek, FlowerService için bir sağlayıcıya sahip bileşen sınıfını gösterir. Bu durumda, enjektör FlowerService'i bulduğu için mevcut ElementInjector'dan daha ileriye bakmaz ve lale 🌷 döndürür.

src/app/self/self.ts

@Component({
  selector: 'app-self',
  templateUrl: './self.html',
  styleUrls: ['./self.css'],
  providers: [{provide: FlowerService, useValue: {emoji: '🌷'}}],
})
export class Self {
  public flower = inject(FlowerService, {self: true});
}

skipSelf

skipSelf, self'in tersidir. skipSelf ile Angular, mevcut elemandan değil üst ElementInjector'dan başlayarak bir servis arar. Yani üst ElementInjector, emoji için eğreltiotu 🌿 değerini kullanıyorsa, ancak bileşenin providers dizisinde akçaağaç yaprağı 🍁 varsa, Angular akçaağaç yaprağını 🍁 yok sayar ve eğreltiotu 🌿 kullanır.

Bunu kodda görmek için, aşağıdaki emoji değerinin üst bileşenin kullandığı değer olduğunu varsayın, bu serviste olduğu gibi:

leaf.service.ts

export class LeafService {
  emoji = '🌿';
}

Alt bileşende farklı bir değeriniz olduğunu, akçaağaç yaprağı 🍁, ancak bunun yerine üst değeri kullanmak istediğinizi hayal edin. skipSelf kullanacağınız yer burasıdır:

skipself.ts

@Component({
  selector: 'app-skipself',
  templateUrl: './skipself.html',
  styleUrls: ['./skipself.css'],
  // Angular bu LeafService örneğini yok sayar
  providers: [{provide: LeafService, useValue: {emoji: '🍁'}}],
})
export class Skipself {
  // skipSelf'i inject seçeneği olarak kullan
  public leaf = inject(LeafService, {skipSelf: true});
}

Bu durumda, emoji için alacağınız değer akçaağaç yaprağı 🍁 değil eğreltiotu 🌿 olacaktır.

skipSelf option with optional

Değer null ise bir hatayı önlemek için skipSelf seçeneğini optional ile kullanın.

Aşağıdaki örnekte, Person servisi özellik başlatma sırasında enjekte edilir. skipSelf, Angular'a mevcut enjektörü atlamasını söyler ve optional, Person servisi null olması durumunda bir hatayı önler.

class Person {
  parent = inject(Person, {optional: true, skipSelf: true});
}

host

host, sağlayıcıları ararken enjektör ağacında bir bileşeni son durak olarak belirlemenize olanak tanır.

Ağaçta daha yukarıda bir servis örneği olsa bile, Angular aramaya devam etmez. host'u şu şekilde kullanın:

host.ts

@Component({
  selector: 'app-host',
  templateUrl: './host.html',
  styleUrls: ['./host.css'],
  // service'i sağla
  providers: [{provide: FlowerService, useValue: {emoji: '🌷'}}],
})
export class Host {
  // service'i enjekte ederken host kullan
  flower = inject(FlowerService, {host: true, optional: true});
}

Host, host seçeneğine sahip olduğundan, Host'un üstünün flower.emoji değeri ne olursa olsun, Host lale 🌷 kullanacaktır.

Constructor injection ile değiştiriciler

Daha önce sunulduğu gibi, constructor enjeksiyonunun davranışı @Optional(), @Self(), @SkipSelf() ve @Host() ile değiştirilebilir.

Her birini @angular/core'dan içe aktarın ve servisinizi enjekte ederken bileşen sınıfı constructor'ında kullanın.

self-no-data.ts

export class SelfNoData {
  constructor(@Self() @Optional() public leaf?: LeafService) {}
}

Şablonun mantıksal yapısı

Bileşen sınıfında servisler sağladığınızda, servisler bu servisleri nerede ve nasıl sağladığınıza göre ElementInjector ağacı içinde görünür olur.

Angular şablonunun temel mantıksal yapısını anlamak, servisleri yapılandırmanız ve bunun karşılığında görünürlüklerini kontrol etmeniz için bir temel sağlar.

Bileşenler, aşağıdaki örnekte olduğu gibi şablonlarınızda kullanılır:

<app-root> <app-child />; </app-root>

HELPFUL: Genellikle bileşenleri ve şablonlarını ayrı dosyalarda bildirirsiniz. Enjeksiyon sisteminin nasıl çalıştığını anlamak amacıyla, bunlara birleşik bir mantıksal ağaç açısından bakmak faydalıdır. Mantıksal terimi bunu render ağacından, yani uygulamanızın DOM ağacından ayırır. Bileşen şablonlarının nerede bulunduğunu işaretlemek için bu kılavuz, render ağacında gerçekte var olmayan ve yalnızca zihinsel model amaçlı olan <#VIEW> sözde elemanını kullanır.

Aşağıda, <app-root> ve <app-child> görünüm ağaçlarının tek bir mantıksal ağaçta nasıl birleştirildiğine dair bir örnek verilmiştir:

<app-root>
  <#VIEW>
    <app-child>
     <#VIEW>
       …content goes here…
     </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

<#VIEW> sınırlamasını anlamak, bileşen sınıfında servisleri yapılandırdığınızda özellikle önemlidir.

Örnek: @Component() içinde service'ler sağlama

@Component() (veya @Directive()) dekoratörünü kullanarak servisleri nasıl sağladığınız, görünürlüklerini belirler. Aşağıdaki bölümler, servis görünürlüğünü skipSelf ve host ile değiştirme yollarıyla birlikte providers ve viewProviders'ı gösterir.

Bir bileşen sınıfı servisleri iki şekilde sağlayabilir:

Arrays Details
With a providers array @Component({ providers: [SomeService] })
With a viewProviders array @Component({ viewProviders: [SomeService] })

Aşağıdaki örneklerde, bir Angular uygulamasının mantıksal ağacını göreceksiniz. Enjektörün şablonlar bağlamında nasıl çalıştığını göstermek için mantıksal ağaç, uygulamanın HTML yapısını temsil edecektir. Örneğin, mantıksal ağaç <child-component>'in <parent-component>'in doğrudan çocuğu olduğunu gösterecektir.

Mantıksal ağaçta özel nitelikler göreceksiniz: @Provide, @Inject ve @ApplicationConfig. Bunlar gerçek nitelikler değildir, arka planda neler olduğunu göstermek için buradalar.

Angular service attribute Details
@Inject(Token)=>Value Mantıksal ağaçtaki bu konumda Token enjekte edilirse, değeri Value olacaktır.
@Provide(Token=Value) Mantıksal ağaçtaki bu konumda Token'ın Value ile sağlandığını gösterir.
@ApplicationConfig Bu konumda yedek bir EnvironmentInjector kullanılması gerektiğini gösterir.

Örnek uygulama yapısı

Örnek uygulamada, root'ta kırmızı hibiskus 🌺 emoji değeri ile sağlanan bir FlowerService vardır.

flower.service.ts

@Service()
export class FlowerService {
  emoji = '🌺';
}

Yalnızca bir App ve bir Child olan bir uygulama düşünün. En temel render edilmiş görünüm, aşağıdaki gibi iç içe HTML elemanları olarak görünecektir:

<app-root>
  <!-- App selector -->
  <app-child> <!-- Child selector --> </app-child>
</app-root>

Ancak arka planda, Angular enjeksiyon isteklerini çözerken aşağıdaki gibi mantıksal bir görünüm temsili kullanır:

<app-root> <!-- App selector -->
  <#VIEW>
    <app-child> <!-- Child selector -->
      <#VIEW>
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

Buradaki <#VIEW> bir şablon örneğini temsil eder. Her bileşenin kendi <#VIEW>'ı olduğuna dikkat edin.

Bu yapıyı bilmek, servislerinizi nasıl sağladığınız ve enjekte ettiğiniz konusunda bilgi verebilir ve servis görünürlüğü üzerinde tam kontrol sağlar.

Şimdi, <app-root>'un FlowerService'i enjekte ettiğini düşünün:

export class App {
  flower = inject(FlowerService);
}

Sonucu görselleştirmek için <app-root> şablonuna bir bağlama ekleyin:

<p>Emoji from FlowerService: {{flower.emoji}}</p>

Görünümdeki çıktı şöyle olacaktır:

Emoji from FlowerService: 🌺

Mantıksal ağaçta bu, aşağıdaki gibi temsil edilir:

<app-root @ApplicationConfig
        @Inject(FlowerService) flower=>"🌺">
  <#VIEW>
    <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>
    <app-child>
      <#VIEW>
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

<app-root> FlowerService'i istediğinde, FlowerService token'ını çözmek enjektörün görevidir. Token'ın çözümlenmesi iki aşamada gerçekleşir:

  1. Enjektör, mantıksal ağaçtaki başlangıç konumunu ve aramanın bitiş konumunu belirler. Enjektör, başlangıç konumundan başlar ve mantıksal ağaçtaki her görünüm seviyesinde token'ı arar. Token bulunursa döndürülür.

  2. Token bulunamazsa, enjektör isteği devretmek için en yakın üst EnvironmentInjector'ı arar.

Örnek durumda, kısıtlamalar şunlardır:

  1. <app-root>'a ait <#VIEW> ile başlayın ve <app-root> ile bitirin.

    • Normalde arama başlangıç noktası enjeksiyon noktasıdır. Ancak bu durumda <app-root> bir bileşendir. @Component'ler özeldir çünkü kendi viewProviders'larını da içerirler, bu nedenle arama <app-root>'a ait <#VIEW>'den başlar. Bu, aynı konumda eşleşen bir direktif için geçerli olmaz.
    • Bitiş konumu, bu uygulamadaki en üst bileşen olduğu için bileşenin kendisiyle aynıdır.
  2. ApplicationConfig tarafından sağlanan EnvironmentInjector, enjeksiyon token'ı ElementInjector hiyerarşilerinde bulunamadığında yedek enjektör olarak hareket eder.

providers dizisini kullanma

Şimdi, Child sınıfında, yaklaşan bölümlerde daha karmaşık çözümleme kurallarını göstermek için FlowerService için bir sağlayıcı ekleyin:

@Component({
  selector: 'app-child',
  templateUrl: './child.html',
  styleUrls: ['./child.css'],
  // bir service sağlamak için providers dizisini kullan
  providers: [{provide: FlowerService, useValue: {emoji: '🌻'}}],
})
export class Child {
  // service'i enjekte et
  flower = inject(FlowerService);
}

Artık FlowerService, @Component() dekoratöründe sağlandığından, <app-child> servisi istediğinde, enjektör yalnızca <app-child> içindeki ElementInjector'a kadar bakmak zorundadır. Enjektör ağacında aramaya devam etmesi gerekmez.

Sonraki adım, Child şablonuna bir bağlama eklemektir.

<p>Emoji from FlowerService: {{flower.emoji}}</p>

Yeni değerleri render etmek için, App şablonunun alt kısmına <app-child> ekleyin, böylece görünüm ayçiçeğini de göstersin:

Child Component
Emoji from FlowerService: 🌻

Mantıksal ağaçta bu, aşağıdaki gibi temsil edilir:

<app-root @ApplicationConfig
          @Inject(FlowerService) flower=>"🌺">
  <#VIEW>

  <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>
  <app-child @Provide(FlowerService="🌻" )
             @Inject(FlowerService)=>"🌻"> <!-- search ends here -->
    <#VIEW> <!-- search starts here -->
    <h2>Child Component</h2>
    <p>Emoji from FlowerService: {{flower.emoji}} (🌻)</p>
  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>

<app-child> FlowerService'i istediğinde, enjektör aramaya <app-child>'a ait <#VIEW>'den başlar (<#VIEW> dahildir çünkü @Component()'den enjekte edilmiştir) ve <app-child> ile biter. Bu durumda, FlowerService, <app-child>'ın ayçiçeği 🌻 ile providers dizisinde çözümlenir. Enjektörün enjektör ağacında daha ileriye bakması gerekmez. FlowerService'i bulur bulmaz durur ve kırmızı hibiskusu 🌺 asla görmez.

viewProviders dizisini kullanma

@Component() dekoratöründe servis sağlamanın başka bir yolu olarak viewProviders dizisini kullanın. viewProviders kullanmak, servisleri <#VIEW> içinde görünür kılar.

HELPFUL: Adımlar, viewProviders dizisini kullanma dışında providers dizisini kullanmakla aynıdır.

Adım adım talimatlar için bu bölümle devam edin. Kendiniz ayarlayabiliyorsanız, Servis kullanılabilirliğini değiştirme bölümüne atlayın.

Gösterim için, viewProviders'ı göstermek amacıyla bir AnimalService oluşturuyoruz. İlk olarak, balina 🐳 emoji özelliğine sahip bir AnimalService oluşturun:

import {Service} from '@angular/core';

@Service()
export class AnimalService {
  emoji = '🐳';
}

FlowerService ile aynı deseni izleyerek, AnimalService'i App sınıfına enjekte edin:

export class App {
  public flower = inject(FlowerService);
  public animal = inject(AnimalService);
}

HELPFUL: Tüm FlowerService ile ilgili kodu yerinde bırakabilirsiniz çünkü AnimalService ile bir karşılaştırma yapmanıza olanak tanıyacaktır.

Bir viewProviders dizisi ekleyin ve AnimalService'i <app-child> sınıfına da enjekte edin, ancak emoji'ye farklı bir değer verin. Burada, köpek 🐶 değerine sahiptir.

@Component({
  selector: 'app-child',
  templateUrl: './child.html',
  styleUrls: ['./child.css'],
  // service'leri sağla
  providers: [{provide: FlowerService, useValue: {emoji: '🌻'}}],
  viewProviders: [{provide: AnimalService, useValue: {emoji: '🐶'}}],
})
export class Child {
  // service'leri enjekte et
  flower = inject(FlowerService);
  animal = inject(AnimalService);
}

Child ve App şablonlarına bağlamalar ekleyin. Child şablonuna aşağıdaki bağlamayı ekleyin:

<p>Emoji from AnimalService: {{animal.emoji}}</p>

Ayrıca aynısını App şablonuna ekleyin:

<p>Emoji from AnimalService: {{animal.emoji}}</p>

Şimdi her iki değeri de tarayıcıda görmelisiniz:

App
Emoji from AnimalService: 🐳

Child Component
Emoji from AnimalService: 🐶

viewProviders'ın bu örneği için mantıksal ağaç aşağıdaki gibidir:

<app-root @ApplicationConfig
          @Inject(AnimalService) animal=>"🐳">
  <#VIEW>
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    @Inject(AnimalService=>"🐶")>

    <!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
    <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>
  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>

FlowerService örneğinde olduğu gibi, AnimalService <app-child> @Component() dekoratöründe sağlanır. Bu, enjektör önce bileşenin ElementInjector'ına baktığından, köpek 🐶 AnimalService değerini bulur. ElementInjector ağacında aramaya devam etmesi gerekmez, ModuleInjector'da da araması gerekmez.

providers ve viewProviders karşılaştırması

viewProviders alanı kavramsal olarak providers'a benzerdir, ancak önemli bir fark vardır. viewProviders'daki sağlayıcılar yalnızca bileşenin kendi görünümü içinde görünürdür; bileşene <ng-content> aracılığıyla yansıtılan içerik bunları göremez.

providers ve viewProviders kullanma arasındaki farkı görmek için, örneğe başka bir bileşen ekleyin ve Inspector olarak adlandırın. Inspector, Child'ın bir çocuğu olacaktır. inspector.ts'de, özellik başlatma sırasında FlowerService ve AnimalService'i enjekte edin:

export class Inspector {
  flower = inject(FlowerService);
  animal = inject(AnimalService);
}

providers veya viewProviders dizisine ihtiyacınız yok. Sonra, inspector.html'de önceki bileşenlerle aynı işaretlemeyi ekleyin:

<p>Emoji from FlowerService: {{flower.emoji}}</p>
<p>Emoji from AnimalService: {{animal.emoji}}</p>

InspectorChild imports dizisine eklemeyi unutmayın.

@Component({
  ...
  imports: [Inspector]
})

Sonra, child.html'e aşağıdakini ekleyin:

...

<div class="container">
  <h3>Content projection</h3>
  <ng-content />
</div>
<h3>Inside the view</h3>

<app-inspector />

<ng-content> içerik yansıtmanıza olanak tanır ve Child şablonu içindeki <app-inspector>, InspectorChild'ın alt bileşeni yapar.

Sonra, içerik yansıtmadan yararlanmak için app.html'e aşağıdakini ekleyin.

<app-child>
  <app-inspector />
</app-child>

Tarayıcı şimdi aşağıdakini render eder, kısalık için önceki örnekler atlanmıştır:

...
Content projection

Emoji from FlowerService: 🌻
Emoji from AnimalService: 🐳

Emoji from FlowerService: 🌻
Emoji from AnimalService: 🐶

Bu dört bağlama, providers ve viewProviders arasındaki farkı gösterir. Köpek emojisinin 🐶 Child'ın <#VIEW> içinde bildirildiğini ve yansıtılmış içerik için görünür olmadığını unutmayın. Bunun yerine, yansıtılmış içerik balinayı 🐳 görür.

Yansıtılan <app-inspector>'ın neden hâlâ App'in viewProviders'ından 🐳 değerini görebildiğini merak edebilirsiniz. Bunun nedeni, Angular DI'nın bir bileşenin nerede render edildiğini değil, nerede bildirildiğini izlemesidir. <app-inspector>, App'in şablonunda, yani App'in <#VIEW> içinde yer alır, bu yüzden App'in viewProviders'ına erişebilir. Onu Child'a yansıtmak, Child'ın viewProviders'ına (🐶) erişimi keser, ancak App'in sağlayıcılarına (🐳) ağaçta yukarı doğru hâlâ erişilebilir.

Ancak sonraki çıktı bölümünde, Inspector Child'ın gerçek bir alt bileşenidir, Inspector <#VIEW> içindedir, bu nedenle AnimalService'i istediğinde köpeği 🐶 görür.

Mantıksal ağaçta AnimalService şöyle görünecektir:

<app-root @ApplicationConfig
          @Inject(AnimalService) animal=>"🐳">
  <#VIEW>
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    @Inject(AnimalService=>"🐶")>

    <!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
    <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>

    <div class="container">
      <h3>Content projection</h3>
      <app-inspector @Inject(AnimalService) animal=>"🐳">
        <p>Emoji from AnimalService: {{animal.emoji}} (🐳)</p>
      </app-inspector>
    </div>

    <app-inspector>
      <#VIEW @Inject(AnimalService) animal=>"🐶">
      <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>
    </
    #VIEW>
    </app-inspector>
  </
  #VIEW>
  </app-child>

</#VIEW>
</app-root>

Yansıtılan <app-inspector>, 🐳 değerini alır çünkü 🐶 Child'ın görünümüne aittir ve yansıtılmış içerik ona erişemez. 🐳 erişilebilirdir çünkü <app-inspector>, App'in şablonunda bildirilmiştir, dolayısıyla App'in viewProviders'ına kadar yukarı çıkabilir.

Child'ın şablonunda doğrudan yer alan (yansıtılmamış) <app-inspector> ise 🐶 değerini alır; <#VIEW> içinde olduğu için aşılması gereken bir sınır yoktur.

Sağlanan token'ların görünürlüğü

Görünürlük dekoratörleri, mantıksal ağaçta enjeksiyon token'ı aramasının nerede başlayıp nerede biteceğini etkiler. Bunu yapmak için, görünürlük yapılandırmasını bir bildirim noktasına değil, enjeksiyon noktasına, yani inject() çağrılırken yerleştirin.

FlowerService için enjektörün aramaya nereden başladığını değiştirmek için, FlowerService'in enjekte edildiği <app-child> inject() çağrısına skipSelf ekleyin. Bu çağrı, child.ts'de gösterildiği gibi <app-child>'ın bir özellik başlatıcısıdır:

flower = inject(FlowerService, {skipSelf: true});

skipSelf ile, <app-child> enjektörü FlowerService için kendisine bakmaz. Bunun yerine, enjektör FlowerService'i <app-root>'un ElementInjector'ında aramaya başlar ve burada hiçbir şey bulamaz. Sonra, <app-child> ModuleInjector'ına geri döner ve kırmızı hibiskus 🌺 değerini bulur, bu kullanılabilir çünkü <app-child> ve <app-root> aynı ModuleInjector'ı paylaşır. Kullanıcı arayüzü aşağıdakini render eder:

Emoji from FlowerService: 🌺

Mantıksal ağaçta, aynı fikir şöyle görünebilir:

<app-root @ApplicationConfig
          @Inject(FlowerService) flower=>"🌺">
  <#VIEW>
  <app-child @Provide(FlowerService="🌻" )>
    <#VIEW @Inject(FlowerService, SkipSelf)=>"🌺">

    <!-- With SkipSelf, the injector looks to the next injector up the tree (app-root) -->

  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>

<app-child> ayçiçeğini 🌻 sağlasa da, skipSelf mevcut enjektörün (app-child) kendisini atlayıp üst elemana bakmasına neden olduğundan uygulama kırmızı hibiskusu 🌺 render eder.

Şimdi host'u (skipSelf'e ek olarak) eklerseniz, sonuç null olacaktır. Bunun nedeni, host'un aramanın üst sınırını app-child <#VIEW> ile sınırlamasıdır. İşte mantıksal ağaçtaki fikir:

<app-root @ApplicationConfig
          @Inject(FlowerService) flower=>"🌺">
  <#VIEW> <!-- end search here with null-->
  <app-child @Provide(FlowerService="🌻" )> <!-- start search here -->
    <#VIEW inject(FlowerService, {skipSelf: true, host: true, optional:true})=>null>
  </
  #VIEW>
  </app-parent>
</#VIEW>
</app-root>

Burada, servisler ve değerleri aynıdır, ancak host enjektörün FlowerService için <#VIEW>'den daha ileriye bakmasını engeller, bu nedenle bulamaz ve null döndürür.

skipSelf and viewProviders

<app-child>'ın AnimalService'i viewProviders dizisinde köpek 🐶 değeri ile sağladığını hatırlayın. Enjektörün AnimalService için yalnızca <app-child>'ın ElementInjector'ına bakması gerektiğinden, balinayı 🐳 asla görmez.

FlowerService örneğinde olduğu gibi, AnimalService'in inject()'ine skipSelf eklerseniz, enjektör AnimalService için mevcut <app-child> ElementInjector'ına bakmaz. Bunun yerine, enjektör <app-root> ElementInjector'ından başlayacaktır.

@Component({
  selector: 'app-child',

  viewProviders: [
    { provide: AnimalService, useValue: { emoji: '🐶' } },
  ],
})

<app-child>'da skipSelf ile mantıksal ağaç şöyle görünür:

<app-root @ApplicationConfig
          @Inject(AnimalService=>"🐳")>
  <#VIEW><!-- search begins here -->
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    @Inject(AnimalService, SkipSelf=>"🐳")>

    <!--Add skipSelf -->

  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>

<app-child>'da skipSelf ile, enjektör AnimalService aramasına <app-root> ElementInjector'ında başlar ve balina 🐳 bulur.

host and viewProviders

AnimalService'in enjeksiyonu için sadece host kullanırsanız, sonuç köpek 🐶 olacaktır çünkü enjektör AnimalService'i <app-child> <#VIEW> içinde bulur. Child, köpek emojisinin AnimalService değeri olarak sağlanması için viewProviders'ı yapılandırır. inject() içinde host'u da görebilirsiniz:

@Component({
  selector: 'app-child',

  viewProviders: [
    { provide: AnimalService, useValue: { emoji: '🐶' } },
  ]
})
export class Child {
  animal = inject(AnimalService, { host: true })
}

host: true, enjektörün <#VIEW>'ün kenarına ulaşana kadar bakmasına neden olur.

<app-root @ApplicationConfig
          @Inject(AnimalService=>"🐳")>
  <#VIEW>
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    inject(AnimalService, {host: true}=>"🐶")> <!-- host stops search here -->
  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>

app.ts @Component() meta verisine üçüncü bir hayvan olan kirpi 🦔 ile bir viewProviders dizisi ekleyin:

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrls: [ './app.css' ],
  viewProviders: [
    { provide: AnimalService, useValue: { emoji: '🦔' } },
  ],
})

Sonra, child.ts'deki AnimalService enjeksiyonunun inject()'ine host ile birlikte skipSelf ekleyin. İşte animal özellik başlatmadaki host ve skipSelf:

export class Child {
  animal = inject(AnimalService, {host: true, skipSelf: true});
}

host ve skipSelf, providers dizisindeki FlowerService'e uygulandığında, sonuç null idi çünkü skipSelf aramasına <app-child> enjektöründe başlar, ancak host aramayı <#VIEW>'de durdurur - burada FlowerService yoktur. Mantıksal ağaçta, FlowerService'in <app-child> içinde görünür olduğunu, <#VIEW> içinde değil görebilirsiniz.

Ancak, App viewProviders dizisinde sağlanan AnimalService görünür durumdadır.

Mantıksal ağaç temsili bunun nedenini gösterir:

<app-root @ApplicationConfig
          @Inject(AnimalService=>"🐳")>
  <#VIEW @Provide(AnimalService="🦔")
  @Inject(AnimalService, @Optional)=>"🦔">

  <!-- ^^skipSelf starts here,  host stops here^^ -->
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    inject(AnimalService, {skipSelf:true, host: true, optional: true})=>"🦔">
    <!-- Add skipSelf ^^-->
  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>

skipSelf, enjektörün AnimalService aramasına isteğin kaynaklandığı <app-child> değil <app-root>'tan başlamasına neden olur ve host, aramayı <app-root> <#VIEW>'de durdurur. AnimalService, viewProviders dizisi aracılığıyla sağlandığından, enjektör <#VIEW> içinde kirpiyi 🦔 bulur.

Örnek: ElementInjector kullanım senaryoları

Farklı seviyelerde bir veya daha fazla sağlayıcı yapılandırma yeteneği yararlı olasılıklar açar.

Senaryo: service izolasyonu

Mimari nedenler, bir servise erişimi ait olduğu uygulama alanıyla sınırlamanıza yol açabilir. Örneğin, kötü karakterlerin bir listesini görüntüleyen bir VillainsList oluşturduğumuzu düşünün. Bu kötü karakterleri bir VillainsService'den alır.

VillainsService'i root AppModule'da sağlarsanız, VillainsService uygulamada her yerde görünür olacaktır. VillainsService'i daha sonra değiştirirseniz, bu servise kazayla bağımlı olmaya başlayan diğer bileşenlerde bir şeyleri bozabilirsiniz.

Bunun yerine, VillainsService'i VillainsList'in providers meta verisinde şu şekilde sağlamalısınız:

@Component({
  selector: 'app-villains-list',
  templateUrl: './villains-list.html',
  providers: [VillainsService],
})
export class VillainsList {}

VillainsService'i VillainsList meta verisinde ve başka hiçbir yerde sağlamayarak, servis yalnızca VillainsList ve alt bileşen ağacında kullanılabilir olur.

VillainsService, bildirildiği yer olduğu için VillainsList'e göre bir tekil örnektir. VillainsList yok edilmediği sürece aynı VillainsService örneği olacaktır, ancak birden fazla VillainsList örneği varsa, her VillainsList örneğinin kendi VillainsService örneği olacaktır.

Senaryo: birden fazla düzenleme oturumu

Birçok uygulama, kullanıcıların aynı anda birkaç açık görev üzerinde çalışmasına olanak tanır. Örneğin, bir vergi hazırlama uygulamasında, hazırlayıcı gün boyunca birinden diğerine geçerek birkaç vergi beyannamesi üzerinde çalışıyor olabilir.

Bu senaryoyu göstermek için, süper kahramanların bir listesini görüntüleyen bir HeroList hayal edin.

Bir kahramanın vergi beyannamesini açmak için, hazırlayıcı bir kahraman adına tıklar ve bu beyanı düzenlemek için bir bileşen açılır. Seçilen her kahraman vergi beyannamesi kendi bileşeninde açılır ve aynı anda birden fazla beyan açık olabilir.

Her vergi beyannamesi bileşeni şu özelliklere sahiptir:

  • Kendi vergi beyannamesi düzenleme oturumudur
  • Başka bir bileşendeki bir beyanı etkilemeden bir vergi beyannamesini değiştirebilir
  • Vergi beyannamesindeki değişiklikleri kaydetme veya iptal etme yeteneğine sahiptir

HeroTaxReturn'ın değişiklikleri yönetme ve geri yükleme mantığına sahip olduğunu varsayalım. Bu, bir kahraman vergi beyannamesi için basit bir görev olurdu. Gerçek dünyada, zengin bir vergi beyannamesi veri modeli ile değişiklik yönetimi karmaşık olurdu. Bu yönetimi, bu örneğin yaptığı gibi bir yardımcı servise devredebilirsiniz.

HeroTaxReturnService, tek bir HeroTaxReturn'ı önbelleğe alır, bu beyandaki değişiklikleri izler ve kaydetme veya geri yükleme yapabilir. Ayrıca enjeksiyon yoluyla aldığı uygulama çapında tekil HeroService'e de delege eder.

import {inject, Service} from '@angular/core';
import {HeroTaxReturn} from './hero';
import {HeroesService} from './heroes.service';

@Service({autoProvided: false})
export class HeroTaxReturnService {
  private currentTaxReturn!: HeroTaxReturn;
  private originalTaxReturn!: HeroTaxReturn;

  private heroService = inject(HeroesService);

  set taxReturn(htr: HeroTaxReturn) {
    this.originalTaxReturn = htr;
    this.currentTaxReturn = htr.clone();
  }

  get taxReturn(): HeroTaxReturn {
    return this.currentTaxReturn;
  }

  restoreTaxReturn() {
    this.taxReturn = this.originalTaxReturn;
  }

  saveTaxReturn() {
    this.taxReturn = this.currentTaxReturn;
    this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe();
  }
}

İşte HeroTaxReturnService'i kullanan HeroTaxReturn.

import {Component, input, output} from '@angular/core';
import {HeroTaxReturn} from './hero';
import {HeroTaxReturnService} from './hero-tax-return.service';

@Component({
  selector: 'app-hero-tax-return',
  templateUrl: './hero-tax-return.html',
  styleUrls: ['./hero-tax-return.css'],
  providers: [HeroTaxReturnService],
})
export class HeroTaxReturn {
  message = '';

  close = output<void>();

  get taxReturn(): HeroTaxReturn {
    return this.heroTaxReturnService.taxReturn;
  }

  taxReturn = input.required<HeroTaxReturn>();

  constructor() {
    effect(() => {
      this.heroTaxReturnService.taxReturn = this.taxReturn();
    });
  }

  private heroTaxReturnService = inject(HeroTaxReturnService);

  onCanceled() {
    this.flashMessage('Canceled');
    this.heroTaxReturnService.restoreTaxReturn();
  }

  onClose() {
    this.close.emit();
  }

  onSaved() {
    this.flashMessage('Saved');
    this.heroTaxReturnService.saveTaxReturn();
  }

  flashMessage(msg: string) {
    this.message = msg;
    setTimeout(() => (this.message = ''), 500);
  }
}

Düzenlenecek vergi beyannamesi, getter ve setter'larla uygulanan input özelliği aracılığıyla gelir. Setter, bileşenin kendi HeroTaxReturnService örneğini gelen beyan ile başlatır. Getter her zaman servisin kahramanın mevcut durumu olarak söylediğini döndürür. Bileşen ayrıca servisten bu vergi beyannamesini kaydetmesini ve geri yüklemesini ister.

Servis uygulama çapında bir tekil olsaydı bu çalışmazdı. Her bileşen aynı servis örneğini paylaşırdı ve her bileşen başka bir kahramana ait vergi beyannamesinin üzerine yazardı.

Bunu önlemek için, HeroTaxReturn'ın bileşen seviyesi enjektörünü, bileşen meta verisindeki providers özelliğini kullanarak servisi sağlayacak şekilde yapılandırın.

providers: [HeroTaxReturnService];

HeroTaxReturn'ın HeroTaxReturnService'in kendi sağlayıcısı vardır. Her bileşen örneğinin kendi enjektörü olduğunu hatırlayın. Servisi bileşen seviyesinde sağlamak, bileşenin her örneğinin servisin özel bir örneğini almasını sağlar. Bu, hiçbir vergi beyannamesinin üzerine yazılmamasını garanti eder.

HELPFUL: Senaryonun geri kalan kodu, belgelerin başka yerlerinde öğrenebileceğiniz diğer Angular özelliklerine ve tekniklerine dayanır.

Senaryo: özelleştirilmiş provider'lar

Bir servisi başka bir seviyede tekrar sağlamanın bir diğer nedeni, bileşen ağacında daha derinde o servisin daha özelleştirilmiş bir uygulamasını ikame etmektir.

Örneğin, lastik servisi bilgileri içeren ve araç hakkında daha fazla ayrıntı sağlamak için diğer servislere bağımlı olan bir Car bileşeni düşünün.

(A) olarak işaretlenen root enjektör, CarService ve EngineService hakkında ayrıntılar için genel sağlayıcılar kullanır.

  1. Car bileşeni (A). Bileşen (A) araç hakkında lastik servisi verilerini görüntüler ve araç hakkında daha fazla bilgi sağlamak için genel servisler belirtir.

  2. Alt bileşen (B). Bileşen (B), bileşen (B)'de olan şeyler için uygun özel yeteneklere sahip CarService ve EngineService için kendi özelleştirilmiş sağlayıcılarını tanımlar.

  3. Bileşen (B)'nin çocuğu olarak alt bileşen (C). Bileşen (C), CarService için kendi, daha da özelleştirilmiş sağlayıcısını tanımlar.

Component A

Component B

Component C

Arka planda, her bileşen o bileşen için tanımlanan sıfır, bir veya daha fazla sağlayıcı ile kendi enjektörünü kurar.

En derin bileşende (C) bir Car örneğini çözümlediğinizde, enjektör şunları üretir:

  • Enjektör (C) tarafından çözümlenen bir Car örneği
  • Enjektör (B) tarafından çözümlenen bir Engine
  • Root enjektör (A) tarafından çözümlenen Tires.

(A) RootInjector

CarService, EngineService, TiresService

(B) ParentInjector

CarService2, EngineService2

(C) ChildInjector

CarService3

(C) Car

(B) Engine

(A) Tires

More on dependency injection