Skip to content

Bölüm 10 / 13

Direktif ve pipe yazımı

Attribute/structural direktif, özel pipe, sorgular.

11 dakikalık okuma

Şimdiye dek Angular'ın hazır yapı taşlarını kullandın; kıdemi ayıran çizgi, kendi yapı taşını yazabilmektir. Üç bileşende aynı hover davranışını, beş şablonda aynı yetki kontrolünü, on yerde aynı metin biçimlemesini tekrarlıyorsan çerçevenin cevabı hazır: davranış direktife, biçimleme pipe'a çıkar. Bu ders attribute ve structural direktif yazmayı, özel pipe'ı, viewChild/contentChild sorgularını ve çok yuvalı içerik projeksiyonunu işler — "custom directive yazdın mı?" mülakat sorusunun tam kapsamı.

01Ne öğreneceksin

  • Direktifin üç türü; bileşenle direktifin gerçek ayrımı
  • Attribute direktif yazmak: host metadata (ve sahadaki @HostBinding/@HostListener)
  • Structural direktif: * şekerinin açılımı, TemplateRef + ViewContainerRef
  • Özel pipe: PipeTransform, saf/saf olmayan bedeli, pipe vs computed kararı
  • Sorgular: viewChild/contentChild (signal tabanlı) — ve "sorgu son çaredir" kuralı
  • Çok yuvalı ng-content; hostDirectives ile davranış kompozisyonu

02Direktif: şablonsuz bileşen

3. derste hazır direktifleri okudun; şimdi tanımı netleştirelim. Angular'da bir DOM elementine davranış bağlayan her şey direktiftir ve üç türü vardır:

TürNe yaparÖrnek
BileşenKendi şablonu olan direktif — evet, bileşen teknik olarak direktifin alt türüdür<app-basvuru-karti>
Attribute direktifVar olan elementin görünümünü/davranışını değiştirir; DOM'a element eklemez[appOdakla], hazırlardan ngClass
Structural direktifDOM'a element ekler/çıkarır — şablonu koşula bağlar*appYetkiliyse, eski dünyadan *ngIf

Ayrım cümlesi mülakat klasiğidir: bileşenin şablonu vardır ve DOM'da kendi elementi olur; direktif, başkasının elementine takılan davranıştır. Ne zaman hangisi sorusunun ölçüsü de bu: görünüm parçası üretiyorsan bileşen; var olan elemente davranış ekliyorsan direktif. "Üç bileşende aynı host dinleyicisi" gördüğün an, 4. hattın DRY refleksi burada direktif çıkarma sinyalidir.

03Attribute direktif yazmak

İlk örnek küçük ama gerçek: açılan panelde imleci doğru alana odaklamak. 8. hatta bunu elle yapıyordun; direktif hâline gelince her formda tek öznitelik:

import { Directive, ElementRef, inject, afterNextRender } from "@angular/core";

@Directive({ selector: "[appOdakla]" })
export class Odakla {
  private el = inject(ElementRef<HTMLInputElement>);

  constructor() {
    afterNextRender(() => this.el.nativeElement.focus());
    // afterNextRender: "DOM tarayıcıya çizildikten sonra" — sunucuda hiç çalışmaz,
    // SSR güvenli (11. derste sebebi tamamlanacak)
  }
}

// kullanım: <input appOdakla formControlName="ad">

ElementRef, direktifin takıldığı elementin kapısıdır — DI bunu direktife otomatik verir (4. dersin hiyerarşisi: her element kendi enjektör düğümüdür). İkinci örnek, host'a bağ kurmayı gösterir; modern yazım host metadata'sıdır:

@Directive({
  selector: "[appVurgu]",
  host: {
    "[class.vurgulu]": "ustunde()",       // host'a property binding
    "(mouseenter)": "ustunde.set(true)",  // host'tan olay dinleme
    "(mouseleave)": "ustunde.set(false)",
  },
})
export class Vurgu {
  ustunde = signal(false);
}

Sahada aynı işin dekoratörlü hâlini okuyacaksın — fikir bire bir aynı:

Modern (host metadata)Sahada okuyacaksın
host: { "[class.vurgulu]": "ustunde()" }@HostBinding("class.vurgulu") ustunde = false;
host: { "(mouseenter)": "gir()" }@HostListener("mouseenter") gir() { … }

Bir sınır çizgisi: elemente stil/sınıf takmak için nativeElement.style'ı elle kurcalamak yerine binding kullan — çerçeve değişiklikleri izleyebilsin. Doğrudan DOM'a yazmak gerekiyorsa bile bunun SSR'da (11. ders) sunucuda çalışmayacağını bilerek afterNextRender içine alırsın; eski kodda aynı amaçla soyutlama katmanı Renderer2 görürsün.

04Structural direktif: *'ın perde arkası

3. derste not düşmüştük: *ngIf'teki yıldız bir şekerdir. Açılımı şudur — ve kendi structural direktifini yazmanın anahtarı budur:

<button *appYetkiliyse="'basvuru.sil'">Sil</button>

<!-- derleyicinin gördüğü gerçek yapı: -->
<ng-template [appYetkiliyse]="'basvuru.sil'">
  <button>Sil</button>
</ng-template>

Yani structural direktif, aslında bir ng-template'e takılan attribute direktiftir; elinde iki araç olur — şablonun tarifi (TemplateRef) ve çizileceği yer (ViewContainerRef) — ve DOM'a ekleyip çıkarma kararı tamamen sana kalır:

@Directive({ selector: "[appYetkiliyse]" })
export class Yetkiliyse {
  private sablon    = inject(TemplateRef<unknown>);
  private konteyner = inject(ViewContainerRef);
  private oturum    = inject(OturumServisi);

  yetki = input.required<string>({ alias: "appYetkiliyse" });
  // alias: *appYetkiliyse="'basvuru.sil'" değerinin bu input'a akmasını sağlar

  constructor() {
    effect(() => {
      this.konteyner.clear();
      if (this.oturum.yetkiler().includes(this.yetki())) {
        this.konteyner.createEmbeddedView(this.sablon);
      }
    });
  }
}

Dersin birikimi burada birleşiyor: inject() (4. ders), signal + effect (9. ders — yetki listesi değişirse görünürlük kendiliğinden güncellenir), değişmez düşünce. İki not:

  • Bu, guard'ın kardeşi ve aynı uyarıyı taşır (7. ders): şablondan düğme gizlemek UX'tir, güvenlik değildir — silme yetkisini API da denetler. Direktifin değeri, bu UX kuralının beş şablonda kopyalanmak yerine tek yerde yaşamasıdır.
  • @if/@for çağında kendi structural direktifine daha az ihtiyaç duyarsın — ama mekanizmayı bilmek iki yerde şart: eski kodu okumak (*ngIf dünyası) ve yetki/özellik-bayrağı gibi kesişen görünürlük kurallarını tek direktifte toplamak.

05Özel pipe — ve pipe vs computed kararı

3. derste hazır pipe'ları kullandın; kendi pipe'ın aynı sözleşmenin bir uygulamasıdır: transform metodu olan sınıf:

import { Pipe, PipeTransform } from "@angular/core";

@Pipe({ name: "kisalt" })
export class KisaltPipe implements PipeTransform {
  transform(deger: string, uzunluk = 40): string {
    return deger.length <= uzunluk ? deger : deger.slice(0, uzunluk - 1) + "…";
  }
}

// şablonda: {{ b.aciklama | kisalt:60 }}

Pipe varsayılanda saftır: Angular, transform'u yalnız girdi referansı değişince yeniden çağırır — 3. derste mutasyon tuzağıyla tanıştığın mekanizmanın kendisi. Bu tembellik, pipe'ı şablonda güvenle kullanılır kılar. pure: false yazarsan pipe her değişiklik kontrolünde çalışır — büyük listede her tetikte yüzlerce çağrı demektir; neredeyse her zaman yanlış araçtır (doğru araç ya saf pipe + değişmez veri, ya da hesabı sınıfa almaktır).

Peki pipe mı, computed mı? İkisi de türev değer üretir; karar ölçüsü kapsamdır:

  • Pipe: uygulama genelinde tekrar eden, girdiden başka şeye bakmayan sunum biçimlemesi — tarih/para biçimi, kısaltma, ad birleştirme. Bir kez yazılır, her şablonda kullanılır, tek başına test edilir.
  • computed: bileşenin/servisin kendi durumundan türeyen değer — filtrelenmiş liste, toplam, seçili öğe (9. ders). Şablon dışında da okunur.

Kokuyu da tanı: şablonda {{ hesapla(b) }} gibi metot çağrısı, saf pipe'ın aksine her kontrolde çalışır — sık düşülen performans hatasıdır; ya pipe'a ya computed'a taşınır.

06Sorgular ve çok yuvalı projeksiyon

Veri binding'le akar — ama bazen elementin kendisi gerekir: odak yönetimi, ölçüm, üçüncü parti bir kütüphaneye DOM verme. Bunun kapısı sorgulardır; modern yazım signal döndürür:

export class BasvuruFormu {
  adKutusu = viewChild.required<ElementRef<HTMLInputElement>>("adKutusu");
  //                                       şablondaki #adKutusu referansı — 3. ders

  hataliAlanaOdaklan() { this.adKutusu().nativeElement.focus(); }
}

viewChild bileşenin kendi şablonuna bakar; contentChild ise ng-content ile dışarıdan yansıtılan içeriğe. (Sahada dekoratörlü @ViewChild okuyacaksın; static seçeneği ve ngAfterViewInit zamanlaması onun dünyasının derdiydi — signal sorgular okunduğu anda günceldir.) Kural yine altitude kuralıdır: sorgu son çaredir — binding'le anlatılabilen hiçbir şey için elemente uzanma; sorgu gördüğün yerde "bu neden binding değil?" diye sor.

2. derste tek yuvalı ng-content görmüştün; gerçek kart bileşenleri çok yuvalıdır — select ile içerik hedeflenir:

<!-- BasvuruKarti şablonu -->
<header><ng-content select="[kart-baslik]" /></header>
<ng-content />                     <!-- seçilmeyen her şey buraya -->
<footer><ng-content select="[kart-eylemler]" /></footer>

<!-- kullanım -->
<app-basvuru-karti>
  <h3 kart-baslik>{{ b.unvan }}</h3>
  <p>{{ b.aciklama | kisalt:80 }}</p>
  <button kart-eylemler (click)="sil.emit(b.id)">Sil</button>
</app-basvuru-karti>

Bu kalıp, sunumsal bileşeni (2. ders) düzen sahibi yapar: kart çerçeveyi ve yerleşimi bilir, içerik çağırana kalır — input patlaması (her yeni ihtiyaca bir input daha) yerine projeksiyon.

07Üretim pratiği: kompozisyon ve disiplin

  • hostDirectives — davranışı miras değil kompozisyonla al: bir bileşen, hazır direktifleri kendi host'una takabilir: @Component({ hostDirectives: [Vurgu] }) — kalıtım zinciri kurmadan (4. hattın "kompozisyon > kalıtım" ilkesi) davranış birleştirme. Tasarım sistemi kuran ekiplerin ana aracıdır.
  • Selector öneki: tüm direktiflerine proje öneki ver (app, kurum kısaltması) — [vurgu] gibi çıplak ad, yarın bir HTML özniteliği ya da kütüphane direktifiyle çakışır.
  • Direktif testi ucuzdur: davranış bileşenden direktife çıkınca, tek başına — sahte host'la — test edilir; aynı davranışı üç bileşende ayrı ayrı test etmekten hem ucuz hem sağlamdır (15. hatta araç setiyle).
  • Ölçülü kullan: her şeyi direktife çıkarmak da bir savrulmadır. Eşik, 4. hattan: üçüncü tekrarda soyutla; tek kullanımlık davranış bileşende kalır.

08Mini alıştırma — Kendi yapı taşların

Yaklaşık 20 dakika · kurulum yok — StackBlitz'te Angular şablonu; başvuru listesi/kartı üstüne kur

  1. appVurgu attribute direktifini host metadata'sıyla yaz (üstüne gelince sınıf ekle); üç karta tek öznitelikle uygula. Aynı direktifi bir de @HostBinding/@HostListener ile yazıp iki yazımı yan yana karşılaştır.
  2. appOdakla'yı yaz (afterNextRender + focus) ve form panelinin ilk alanına tak; panel her açılışında odağın oturduğunu klavyeyle doğrula.
  3. *appYetkiliyse structural direktifini yaz: yetkiler localStorage'dan okunan bir signal'da dursun; "Sil" düğmesine *appYetkiliyse="'basvuru.sil'" tak. Konsoldan yetkiyi değiştirip signal'ı güncelle — düğmenin DOM'a girip çıktığını Elements sekmesinde gör (gizlenmiyor, hiç yok).
  4. kisalt pipe'ını yaz ve kart açıklamasına uygula. Sonra transform içine console.count("kisalt") koy; sayfada gezinip saf pipe'ın yalnız girdi değişince çalıştığını say. pure: false yapıp sayacın nasıl patladığını gör — ve geri al.
  5. Kartı çok yuvalı projeksiyona çevir (kart-baslik / gövde / kart-eylemler); iki farklı kullanımda farklı eylemler yansıt.
  6. viewChild.required ile "yeni başvuru" düğmesine basınca formun ilk alanına odaklan; sorgunun neden binding'le yapılamadığını bir cümleyle yaz.

09Kendini sına

Direktif türlerini ve bileşen/direktif ayrımını anlat. Ne zaman direktif yazarsın?

Üç tür vardır ve teknik olarak bileşen de bir direktiftir — şablonu olan tek tür. Bileşen kendi elementini ve görünümünü üretir (<app-basvuru-karti>); attribute direktif, var olan elementin görünüm ve davranışını değiştirir ama DOM'a element eklemez (ngClass, kendi yazdığım appOdakla); structural direktif DOM'a element ekler ve çıkarır — şablonu koşula bağlar (*ngIf, *appYetkiliyse). Ayrım cümlesi: bileşen görünüm üretir, direktif başkasının elementine davranış takar. Direktif yazma sinyali DRY'dan gelir: aynı host dinleyicisi/sınıf bağlaması üç bileşende tekrarlanıyorsa (hover davranışı, odak yönetimi, yetkiye göre görünürlük) davranış direktife çıkar — tek yerde yaşar, tek başına test edilir, hostDirectives ile bileşenlere kompozisyonla takılır. Ölçü de DRY'ın ölçüsüdür: üçüncü tekrarda soyutla; tek kullanımlık davranış bileşende kalır.

Structural direktif nasıl çalışır? * işaretinin açılımını ve TemplateRef/ViewContainerRef ikilisini anlat.

Yıldız bir derleyici şekeridir: *appYetkiliyse="..." yazıldığında derleyici elementi bir ng-template'e sarar ve direktifi o şablona attribute olarak bağlar — yani structural direktif, aslında ng-template'e takılmış bir attribute direktiftir. İçeride DI'dan iki şey istenir: TemplateRef — sarılan şablonun tarifi, henüz çizilmemiş hâli — ve ViewContainerRef — o şablonun çizileceği konteyner. Karar direktifindir: createEmbeddedView(sablon) şablonu DOM'a diker, clear() söker. Görünürlük CSS ile gizlenmez, element gerçekten var olmaz — Elements sekmesinde kanıtlanır. Modern yazımda koşul bir signal'a bağlanır ve effect içinde konteyner güncellenir; koşul değişince görünürlük kendiliğinden düzelir. İki ek not: @if/@for çağında kendi structural direktifine daha az ihtiyaç vardır ama eski kodda *ngIf dünyasını okumak ve yetki/özellik bayrağı gibi kesişen kuralları tek direktifte toplamak için mekanizma bilinmelidir; ve şablondan düğme gizlemek UX'tir — asıl yetki denetimi API'dedir, guard'daki kuralın aynısı.

Saf ve saf olmayan pipe farkı nedir? Pipe, computed ve şablonda metot çağrısı arasında nasıl seçim yaparsın?

Saf pipe (varsayılan), transform'u yalnız girdi referansı değiştiğinde çağırır — bu tembellik onu büyük şablonlarda güvenli kılar ama değişmez veri sözleşmesini şart koşar: diziyi yerinde değiştiren kod, saf pipe'ın gözünde hiçbir şey değiştirmemiştir. pure: false pipe'ı her değişiklik kontrolünde çalıştırır; büyük listede her tetikte yüzlerce çağrı demektir ve neredeyse her zaman yanlış araçtır — doğrusu ya saf pipe + değişmez güncelleme ya da hesabı sınıfa almaktır. Aynı ailedendir: şablonda {{ hesapla(b) }} metot çağrısı da her kontrolde çalışır — sık düşülen performans hatası; ya pipe'a ya computed'a taşınır. Seçim ölçüsü kapsamdır: uygulama genelinde tekrar eden, yalnız girdisine bakan sunum biçimlemesi (tarih, para, kısaltma) pipe olur — bir kez yazılır, her şablonda kullanılır, tek başına test edilir; bileşenin veya servisin kendi durumundan türeyen değer (filtrelenmiş liste, toplam) computed olur — önbelleklidir, şablon dışında da okunur.

viewChild ile contentChild farkı nedir? Çok yuvalı ng-content ne zaman doğru araçtır?

İkisi de sorgu — elementin/bileşenin kendisine erişim — sağlar; fark baktıkları yerdedir: viewChild bileşenin kendi şablonunu sorgular (şablon referansı #adKutusu ya da alt bileşen tipi), contentChild ise ng-content ile dışarıdan yansıtılmış içeriği. Modern yazım signal döndürür (viewChild.required<ElementRef>("adKutusu")) ve okunduğu anda günceldir; sahadaki @ViewChild dekoratörünün static seçeneği ve ngAfterViewInit zamanlama dertleri o eski dünyaya aittir. Kural: sorgu son çaredir — binding'le anlatılabilen hiçbir iş için elemente uzanılmaz; meşru işleri odak yönetimi, ölçüm ve üçüncü parti DOM entegrasyonudur. Çok yuvalı ng-content ise sunumsal bileşenin input patlamasına çözümdür: kart gibi bir düzen bileşeni select="[kart-baslik]" / [kart-eylemler] yuvalarıyla çerçeveyi sahiplenir, içeriği çağırana bırakır — her yeni ihtiyaç için yeni bir input açmak yerine projeksiyon. Böylece bileşen sözleşmesi "veri + olay + yuvalar" üçlüsüne oturur.

10Sınav köşesi

Yazılıda nasıl sorulur

(1) Direktif nedir; kaç türü vardır? (2) Bileşen ile direktif arasındaki fark nedir? (3) Attribute direktif nasıl yazılır; host metadata / @HostBinding/@HostListener ne işe yarar? (4) * öneki derleyicide neye açılır? (5) TemplateRef ve ViewContainerRef nedir? (6) Saf pipe ile saf olmayan pipe farkı nedir; performans etkisi nedir? (7) Şablonda metot çağrısı neden sakıncalıdır? (8) viewChild ile contentChild farkı nedir? (9) Çok yuvalı ng-content nasıl kurulur? (10) hostDirectives ne sağlar?

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Tanım — "Direktif, elemente takılan davranıştır: attribute türü elementi değiştirir, structural türü DOM'a ekler/çıkarır; bileşen, şablonu olan direktiftir." (2) Yazım — "Attribute direktifte host metadata ile bağ ve olay kurarım; DOM'a elle dokunacaksam afterNextRender — SSR güvenli." (3) Mekanizma — "Yıldız şekerdir: ng-template'e sarılır; TemplateRef tarif, ViewContainerRef sahnedir — createEmbeddedView/clear kararı bende. Yetki direktifi yazarım ama bunun UX olduğunu, asıl denetimin API'de olduğunu bilirim." (4) Pipe — "Saf pipe referans değişiminde çalışır — değişmezlik sözleşmesi; pure:false ve şablonda metot çağrısı aynı performans hatasının iki yüzüdür; genel biçimleme pipe, bileşen türevi computed." (5) Kıdem vuruşu — "Sorgu son çaredir — viewChild'ı odak/ölçüm/entegrasyonda kullanırım; kartları çok yuvalı projeksiyonla kurarım; davranışı hostDirectives ile kompoze ederim — kalıtımla değil."