Bölüm 7 / 13
Routing
Rota tanımı, lazy loading, guard'lar.
12 dakikalık okuma
SPA tek HTML ile açılır (1. ders) — peki adres çubuğundaki
/basvurular/42 nasıl çalışır, geri tuşu neden bozulmaz, F5 neden 404
vermez? Cevapların tamamı router'dadır: URL'yi bileşene eşleyen, geçişleri
History API ile sayfayı yenilemeden yapan ve URL'yi uygulama durumunun adresi
hâline getiren katman. Bu ders rota tanımını, parametre okumayı, lazy loading'i ve
guard'ları işler; 1. dersten açık kalan SPA fallback senedi burada kapanır.
01Ne öğreneceksin
- Rota tanımı,
provideRouter,<router-outlet>; eşleşme sırası, yönlendirme ve 404 routerLinkilehreffarkı; programatik gezinme; SPA fallback'in kapanışı- Parametreli rota:
input()ile modern okuma,ActivatedRouteve "bileşen yeniden kullanılır" tuzağı - Lazy loading (
loadComponent) ve rota düzeyiproviders— 4. dersin tablosu tamamlanıyor - Guard'lar:
CanActivateFn,UrlTreeile yönlendirme; guard'ın güvenlik olmadığı gerçeği - URL'yi durumun adresi olarak tasarlamak: sorgu parametreleri, paylaşılabilir ekranlar
02Rota tanımı: URL → bileşen eşlemesi
Router'ın çekirdeği bir eşleme tablosudur — hangi yol hangi bileşeni çizer:
// app.routes.ts
import { Routes } from "@angular/router";
export const rotalar: Routes = [
{ path: "", component: AnaSayfa },
{ path: "basvurular", component: BasvuruListesi },
{ path: "basvurular/:id", component: BasvuruDetayi }, // :id — parametre
{ path: "giris", component: GirisSayfasi },
{ path: "eski-liste", redirectTo: "basvurular", pathMatch: "full" },
{ path: "**", component: BulunamadiSayfasi }, // joker — EN SONDA
];
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [provideRouter(rotalar, withComponentInputBinding())],
};
Kök şablonda <router-outlet /> durur: eşleşen bileşen oraya
çizilir; dışında kalanlar (üst bar, yan menü) her rotada sabittir. Üç kural sahada
ısırır:
- İlk eşleşen kazanır — tablo yukarıdan aşağı taranır.
**jokeri en üste yazan, tüm siteyi 404 yapar; her zaman en sonda durur. pathMatch: "full"tuzağı:redirectTo'lu boş yol (path: "") varsayılan eşleşmeyle ("prefix") her URL'ye uyar — boş dize her yolun önekidir. Boş yoldan yönlendirme her zamanpathMatch: "full"ister; unutulursa hata mesajı bile açıkça bunu söyler.- 404 bir rotadır: "bulunamadı" ekranı tasarlanır (6. hatta API için ne yaptıysan) — joker rota, kullanıcıya listeye dönüş yolu gösteren gerçek bir bileşene gider, boş beyaz sayfaya değil.
03routerLink vs href — ve fallback'in kapanışı
<!-- YANLIŞ (SPA içinde): tam sayfa yenileme -->
<a href="/basvurular">Başvurular</a>
<!-- DOĞRU: History API ile yerinde geçiş -->
<a routerLink="/basvurular" routerLinkActive="aktif">Başvurular</a>
<a [routerLink]="['/basvurular', b.id]">{{ b.unvan }}</a>
href, tarayıcıya klasik komut verir: sayfayı bırak, sunucudan
yeniden iste. SPA için bu küçük bir kusur değil ölümdür: bellekteki tüm durum
(servislerdeki listeler, form içerikleri) sıfırlanır, paket yeniden iner, uygulama
baştan açılır. routerLink ise tıklamayı yakalar, History API'ye yeni
kaydı yazar (pushState — 8. hattın tarayıcı bilgisi) ve yalnız outlet
içeriğini değiştirir. Geri/ileri tuşları çalışır çünkü router bu geçmişi dinler.
Koddan gezinmek de aynı kapıdan geçer:
export class BasvuruKarti {
private router = inject(Router);
ac(id: number) { this.router.navigate(["/basvurular", id]); }
}
Şimdi 1. dersin senedi: kullanıcı /basvurular/42'yi adres
çubuğuna yazarsa ya da orada F5'e basarsa, tarayıcı History API'yi hiç bilmez —
sunucuya gider. Sunucuda basvurular/42 diye bir dosya yoktur;
dürüst bir statik sunucu 404 döner. Bu yüzden SPA barındırmanın altın kuralı:
eşleşmeyen her yol için index.html dön (fallback). O zaman zincir
kurulur: sunucu index.html'i verir → Angular yüklenir → router adres çubuğundaki
URL'yi okur → doğru bileşeni çizer. Yani aynı URL'nin iki cevaplayıcısı vardır:
ilk yüklemede sunucu, sonrasında router. Fallback unutulmuş SPA'nın belirtisi
klasiktir: site içinde gezerken her şey çalışır, F5'te ya da paylaşılan bağlantıda
404 — artık sebebini iki cümlede açıklayabilirsin.
04Parametre okumak — ve yeniden kullanım tuzağı
withComponentInputBinding() kayıtlıysa modern yol zarif:
rota parametresi, 2. dersten tanıdığın input()'a bağlanır:
export class BasvuruDetayi {
id = input.required<string>(); // URL'deki :id buraya akar — DİKKAT: string!
// sorgu parametresi de aynı yolla bağlanır: ?sekme=notlar → sekme = input<string>()
}
İki ayrıntı kıdem ayırt eder: parametre her zaman string gelir — URL metindir;
42 beklediğin yerde "42" vardır, sayıya çevirip doğrulamak
sana düşer (sınırda doğrula — 9. hat). İkincisi, klasik yol olan
ActivatedRoute'u da okuyabilmelisin, çünkü sahadaki kodun çoğu ondadır:
export class BasvuruDetayi {
private rota = inject(ActivatedRoute);
// Anlık okuma — kolay ama eksik:
id1 = this.rota.snapshot.paramMap.get("id");
// Akış olarak okuma — doğru genel çözüm:
basvuru$ = this.rota.paramMap.pipe(
map((p) => p.get("id")!),
switchMap((id) => this.servis.getir(id)), // 5-6. derslerin kalıbı
);
}
Tuzak şu: /basvurular/42'den /basvurular/43'e
geçişte router, aynı bileşen sınıfı eşleştiği için bileşeni yıkmaz — yeniden
kullanır. Kurucu ve ngOnInit ikinci kez çalışmaz. Parametreyi
snapshot'tan bir kez okuyan detay sayfası, "önceki/sonraki başvuru"
bağlantılarında ekranı güncellemez — veri 42'de asılı kalır. Çözüm, parametreyi
akış olarak dinlemektir (paramMap Observable'dır) ya da
input() bağlamayı kullanmaktır — router her geçişte input'u günceller,
sen 9. derste göreceğin gibi ona tepki verirsin. snapshot yalnız
"bu bileşene aynı rotada parametre değişerek dönülemez" diyebildiğin ekranlarda
savunulabilir.
Sorgu parametreleri de aynı API'dedir (queryParamMap) ve görevleri
farklıdır: :id hangi kaynak sorusunu, ?durum=acik&sayfa=2
hangi görünüm sorusunu cevaplar. Filtre ve sayfa durumunu URL'de tutan ekran,
yenilemeye dayanır ve paylaşılabilir — arkadaşına attığın bağlantı senin
gördüğün ekranı açar. "Filtre neden F5'te sıfırlanıyor?" sorusunun cevabı, filtrenin
bileşen alanında değil URL'de yaşaması gerektiğidir.
05Lazy loading ve rota providers
1. derste üretim çıktısını görmüştün: tek paket büyüdükçe ilk açılış ağırlaşır ve bütçeler ihlal edilir. Kullanıcının belki hiç açmayacağı yönetim ekranlarını ilk pakete koymak israftır. Router'ın çözümü, rotayı tembel yüklemektir:
export const rotalar: Routes = [
{ path: "basvurular",
loadComponent: () => import("./basvuru/basvuru-listesi")
.then((m) => m.BasvuruListesi) },
// Bir özelliğin TÜM rotaları tek dosyada — ve o alt ağaca özel servislerle:
{ path: "yonetim",
providers: [YonetimServisi], // rota düzeyi DI — 4. dersin tablosu
loadChildren: () => import("./yonetim/yonetim.routes")
.then((m) => m.YONETIM_ROTALARI) },
];
import() — 8. hattın 18. dersindeki dinamik içe aktarma — derleyiciye
"bunu ayrı parça (chunk) yap" der: kod, rotaya ilk girişte ağdan iner.
Network sekmesinde bunu gözle görürsün — rotaya tıklayınca yeni bir
.js dosyası yüklenir. Karar ölçüsü sadedir: özellik sınırından
böl (yönetim, raporlar, ayarlar), her bileşeni tek tek bölme — parça başına ek
istek maliyeti vardır. Sık kullanılan rotalarda gecikmeyi yumuşatmak için preloading
stratejileri vardır (boşta kalan zamanda arka planda indir); varlığını bil,
ihtiyaçta aç.
providers satırı, 4. dersteki hiyerarşi tablosunun "rota düzeyi"
boşluğunu doldurur: servis o rota alt ağacı yaşadıkça yaşar — bir özelliğin durumunu
uygulama köküne sızdırmadan, özelliğin kapısına asmanın yolu. Lazy rotayla birleşince
servis kodu da davranışı da o özelliğin sınırında kalır.
06Guard: kapıdaki fonksiyon
Bazı rotalar şartlıdır: girişsiz kullanıcı başvuru ekranına gelmesin, kaydedilmemiş formu olan kullanıcı yanlışlıkla sayfadan çıkmasın. Router'ın kapı mekanizması guard'dır — modern yazımda sade bir fonksiyon:
import { CanActivateFn, Router } from "@angular/router";
export const girisliMi: CanActivateFn = (rota, durum) => {
const oturum = inject(OturumServisi); // fonksiyonda inject — 4. dersin senedi
const router = inject(Router);
if (oturum.girisliMi()) return true;
return router.parseUrl("/giris"); // false değil, UrlTree: yönlendir
};
// kullanım: { path: "basvurular", component: ..., canActivate: [girisliMi] }
Dönüş tipi düşünülmüş bir tasarımdır: true geçirir, false
olduğu yerde durdurur (kullanıcı boş ekrana bakar — nadiren istediğin şey),
UrlTree ise geçişi iptal edip başka rotaya yönlendirir — doğru
kalıp neredeyse her zaman budur. Asenkron da olabilir: Observable/Promise dönebilir,
router cevabı bekler. İkinci sık guard, CanDeactivateFn'dir: rotadan
çıkışı denetler — "kaydedilmemiş değişiklik var, çıkılsın mı?" sorusunu 8.
derste yazacağın forma bağlayacaksın.
Kıdem cümlesi — guard güvenlik değildir: guard, istemcide çalışan JavaScript'tir; DevTools açan herkes onu aşabilir, paketteki her kod kullanıcının elindedir (1. ders: pakete sır konmaz). Guard kullanıcı deneyimi aracıdır — yetkisiz kullanıcıyı boş ekranla değil giriş sayfasıyla karşılamak için. Asıl yetki denetimi sunucudadır: API her istekte kimliği doğrular (6. hatta 401/403, ayrıntısı 13. hatta). "Rotayı guard'ladım, veri güvende" cümlesi, mülakatta eleyen cevaptır; doğrusu "guard UX, yetki sunucuda" ikilisidir.
07Üretim pratiği: URL bir tasarım yüzeyidir
- URL'yi durumun adresi yap: hangi kayıt (
:id), hangi görünüm (?durum=acik&sayfa=2) URL'den okunabilmeli. Ölçü şu: kullanıcı adres çubuğunu kopyalayıp gönderdiğinde karşı taraf aynı ekranı görüyor mu? 6. hatta API URL'lerini kaynak diliyle tasarladın; ekran URL'leri de aynı özeni hak eder. - Rota dosyalarını özellik sınırından böl: tüm rotaları tek dosyada
yığmak yerine
basvuru.routes.ts,yonetim.routes.ts— lazyloadChildrenzaten bu bölmeyi teşvik eder. - Sayfa başlığını rotayla değiştir: rota tanımındaki
titlealanı tarayıcı sekmesine yazılır — sekmeler arasında gezinen kullanıcı ve ekran okuyucu için küçük ama gerçek bir fark. - Detay bileşenini akışla düşün: parametre değişiminde yeniden kullanım
varsayılan davranıştır; detay sayfası kurarken ilk soru "parametre değişirse ne
olur?" olmalı.
input()+ signal tepkisi (9. ders) bu soruyu baştan doğru cevaplar.
08Mini alıştırma — Üç rotalı başvuru gezgini
Yaklaşık 20 dakika · kurulum yok — StackBlitz'te Angular şablonu; önceki derslerin BasvuruServisi'ni taban al
- Rotaları kur:
""→ liste,basvurular/:id→ detay,**→ 404 bileşeni.withComponentInputBinding()ile kaydet; listedeki her kartı[routerLink]ile detaya bağla. - href deneyi: listeye bir sayaç koy (her tıklamada artan basit alan).
Önce
hrefile bir bağlantı ver, tıkla — sayacın sıfırlandığını gör;routerLink'e çevir, durumun yaşadığını doğrula. Farkı bir cümleyle yaz. - Detayda parametreyi iki yolla oku: önce
snapshotile; detaya "sonraki başvuru" bağlantısı koy ([routerLink]="['/basvurular', id+1]") ve ekranın güncellenmediğini gör. Sonrainput.required(ya daparamMapakışı) ile düzelt. - 404 rotasını test et: adres çubuğuna saçma bir yol yaz; joker bileşenin çizildiğini ve içinde listeye dönüş bağlantısı olduğunu doğrula. Jokeri dizinin başına taşı, tüm sitenin 404'e düştüğünü gör, geri al.
- Guard yaz:
localStorage'dagirislibayrağına bakangirisliMi; yoksaparseUrl("/giris"). Detay rotasına bağla; giriş sayfasına bayrağı yazan bir buton koy ve iki durumu da dene. - Lazy deneyi: 404 (ya da "hakkında") bileşenini
loadComponent'e çevir; Network sekmesinde rotaya ilk girişte inen ayrı.jsparçasını yakala.
09Kendini sına
SPA'da routing nasıl çalışır? Site içinde her şey çalışırken F5'te 404 gelmesinin sebebi nedir?
SPA tek index.html ile açılır; sonrasında sayfa hiç
yenilenmez. Router, rota tablosunu (URL deseni → bileşen eşlemesi) tutar;
routerLink tıklamayı yakalar, History API'ye (pushState)
yeni kaydı yazar ve yalnız router-outlet içeriğini değiştirir — geri/
ileri tuşları da bu geçmiş üzerinden çalışır. Yani gezinme sırasında URL değişir ama
sunucuya istek gitmez. F5 ve doğrudan girilen bağlantı bu mekanizmanın dışındadır:
tarayıcı /basvurular/42'yi sunucudan ister; sunucuda böyle bir
dosya yoktur ve fallback yapılandırılmamışsa 404 döner — "site içinde çalışıyor,
yenileyince kırılıyor" belirtisinin tek sebebi budur. Çözüm barındırma kuralıdır:
eşleşmeyen her yola index.html dönülür; Angular yüklenir, router adres çubuğunu
okuyup doğru bileşeni çizer. Özet cümle: aynı URL'ye ilk yüklemede sunucu, sonraki
geçişlerde router cevap verir; fallback bu ikiliyi birbirine bağlayan sözleşmedir.
Rota parametresini okuma yolları nelerdir? "Bileşen yeniden kullanılır" tuzağını anlat.
Üç yol var. Modern yol, withComponentInputBinding
kayıtlıyken parametrenin bileşen input()'una bağlanmasıdır — router her
geçişte input'u günceller. Klasik yol ActivatedRoute'tur ve ikiye
ayrılır: snapshot.paramMap anlık değeri verir; paramMap
Observable'dır, her parametre değişiminde yayınlar. Tuzak şurada: aynı rota
deseninde parametre değişerek gezinildiğinde (42 → 43) router bileşeni yıkmaz,
yeniden kullanır — kurucu ve ngOnInit tekrar çalışmaz.
Parametreyi snapshot'tan bir kez okuyan detay sayfası bu geçişte güncellenmez ve
eski kaydı göstermeye devam eder; "önceki/sonraki" bağlantıları olan her ekranda
bu kaçınılmaz olarak yaşanır. Doğrusu parametreyi akış olarak ele almaktır:
paramMap.pipe(switchMap(id => servis.getir(id))) — switchMap
seçimi de bilinçlidir, hızlı gezinmede önceki isteği iptal eder. İki ek ayrıntı:
parametre her zaman string gelir, sayıya çevirip doğrulamak bize düşer; ve
:id hangi kaynak sorusunu, sorgu parametreleri hangi görünüm sorusunu
cevaplar — filtre/sayfa gibi görünüm durumu URL'de tutulursa ekran yenilemeye
dayanıklı ve paylaşılabilir olur.
Lazy loading nedir, ne kazandırır? Rota düzeyi providers ne işe yarar?
Varsayılanda tüm bileşenler tek pakete girer ve ilk açılışta
iner; kullanıcının belki hiç açmayacağı ekranlar da bu bedele dahildir. Lazy
loading, rotanın kodunu loadComponent / loadChildren ve
dinamik import() ile ayrı parçaya (chunk) böler: kod, rotaya ilk
girişte ağdan iner — Network sekmesinde o an inen ayrı .js dosyası olarak
gözlemlenebilir. Kazanç ilk paket boyutu ve açılış süresidir; üretim bütçeleri bu
yüzden lazy sınırlarıyla birlikte düşünülür. Karar ölçüsü özellik sınırıdır:
yönetim, raporlar gibi bölgeler bölünür; her küçük bileşeni bölmek parça başına ek
istek getirir. Sık girilen lazy rotalar için preloading (boş zamanda arka planda
indirme) vardır. Rota düzeyi providers ise hiyerarşik DI'ın rota
katmanıdır: servis o rota alt ağacı yaşadıkça yaşar — özelliğe özel durum, uygulama
köküne sızdırılmadan özelliğin kapısında sağlanır; lazy loadChildren
ile birleşince bir özelliğin hem kodu hem servisleri kendi sınırında toplanmış
olur.
Guard nedir, nasıl yazılır? "Guard'la korudum, güvenli" cümlesinin nesi yanlış?
Guard, rotaya giriş/çıkışı denetleyen fonksiyondur. Modern
yazım fonksiyoneldir: CanActivateFn tipinde bir fonksiyon, injection
context'te çalıştığı için gövdesinde inject() ile servis alır;
true dönerse geçiş olur, false geçişi olduğu yerde
durdurur, UrlTree (router.parseUrl("/giris")) ise iptal
edip yönlendirir — kullanıcıyı boş ekranda bırakmamak için doğru kalıp neredeyse
her zaman UrlTree'dir; asenkron kaynak gerekiyorsa Observable/Promise de
dönebilir. CanDeactivateFn çıkışı denetler — kaydedilmemiş form
uyarısının mekanizması. Yanlışlık şurada: guard istemcide çalışan JavaScript'tir;
paket kullanıcının elindedir, DevTools'la aşılabilir. Guard bir kullanıcı
deneyimi aracıdır — yetkisiz kullanıcıyı düzgün bir giriş ekranına götürür;
güvenlik sınırı değildir. Asıl yetki denetimi sunucudadır: API her isteği
kimlik ve yetki açısından kendisi doğrular, istemci ne gösterirse göstersin.
Doğru zihin modeli: guard kapıdaki tabela, sunucu kapıdaki kilittir; ikisi
birlikte kurulur, biri diğerinin yerine geçmez.
10Sınav köşesi
Yazılıda nasıl sorulur
(1) SPA'da routing nasıl çalışır; History API'nin rolü nedir? (2)
routerLink ile href farkı nedir? (3) Rota tablosunda
eşleşme sırası ve ** jokeri nasıl çalışır? (4) pathMatch:
"full" ne zaman zorunludur? (5) Rota parametresi okuma yolları nelerdir;
snapshot'ın riski nedir? (6) Lazy loading nedir; hangi sınırdan bölünür? (7)
Rota düzeyi providers ne sağlar? (8) CanActivateFn'in
dönüş tipleri ve anlamları nelerdir? (9) Guard neden güvenlik önlemi değildir?
(10) SPA fallback nedir; neden gerekir?
Sözlüde nasıl anlatılır — 2 dakikalık taslak
Düzen: (1) Tanım — "Router, URL'yi bileşene eşler; geçişler History API ile sayfa yenilenmeden olur; URL, uygulama durumunun adresidir." (2) İki cevaplayıcı — "İlk yüklemede sunucu, sonrasında router cevap verir; bu yüzden sunucuda SPA fallback şarttır — F5'te 404'ün tek sebebi budur." (3) Parametre — "Detayı akışla kurarım: aynı bileşen parametre değişince yeniden kullanılır, snapshot eski kayıtta asılı bırakır; paramMap + switchMap ya da input binding." (4) Ölçek — "Özellik sınırından lazy bölerim: loadChildren + rota providers; ilk paket küçülür, özelliğin kodu ve servisleri kendi sınırında kalır." (5) Kıdem vuruşu — "Guard UX'tir, güvenlik sunucudadır; UrlTree ile yönlendiririm, false ile kullanıcıyı boşlukta bırakmam; filtre/sayfa durumunu URL'de tutarım — ekran paylaşılabilir olur."
