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:
hostmetadata (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;hostDirectivesile 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ür | Ne yapar | Örnek |
|---|---|---|
| Bileşen | Kendi şablonu olan direktif — evet, bileşen teknik olarak direktifin alt türüdür | <app-basvuru-karti> |
| Attribute direktif | Var olan elementin görünümünü/davranışını değiştirir; DOM'a element eklemez | [appOdakla], hazırlardan ngClass |
| Structural direktif | DOM'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 (*ngIfdü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
appVurguattribute direktifinihostmetadata'sıyla yaz (üstüne gelince sınıf ekle); üç karta tek öznitelikle uygula. Aynı direktifi bir de@HostBinding/@HostListenerile yazıp iki yazımı yan yana karşılaştır.appOdakla'yı yaz (afterNextRender+ focus) ve form panelinin ilk alanına tak; panel her açılışında odağın oturduğunu klavyeyle doğrula.*appYetkiliysestructural direktifini yaz: yetkilerlocalStorage'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).kisaltpipe'ını yaz ve kart açıklamasına uygula. Sonratransformiçineconsole.count("kisalt")koy; sayfada gezinip saf pipe'ın yalnız girdi değişince çalıştığını say.pure: falseyapıp sayacın nasıl patladığını gör — ve geri al.- Kartı çok yuvalı projeksiyona çevir (
kart-baslik/ gövde /kart-eylemler); iki farklı kullanımda farklı eylemler yansıt. viewChild.requiredile "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."
