Skip to content

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
  • routerLink ile href farkı; programatik gezinme; SPA fallback'in kapanışı
  • Parametreli rota: input() ile modern okuma, ActivatedRoute ve "bileşen yeniden kullanılır" tuzağı
  • Lazy loading (loadComponent) ve rota düzeyi providers — 4. dersin tablosu tamamlanıyor
  • Guard'lar: CanActivateFn, UrlTree ile 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 zaman pathMatch: "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 — lazy loadChildren zaten bu bölmeyi teşvik eder.
  • Sayfa başlığını rotayla değiştir: rota tanımındaki title alanı 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

  1. Rotaları kur: "" → liste, basvurular/:id → detay, ** → 404 bileşeni. withComponentInputBinding() ile kaydet; listedeki her kartı [routerLink] ile detaya bağla.
  2. href deneyi: listeye bir sayaç koy (her tıklamada artan basit alan). Önce href ile 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.
  3. Detayda parametreyi iki yolla oku: önce snapshot ile; detaya "sonraki başvuru" bağlantısı koy ([routerLink]="['/basvurular', id+1]") ve ekranın güncellenmediğini gör. Sonra input.required (ya da paramMap akışı) ile düzelt.
  4. 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.
  5. Guard yaz: localStorage'da girisli bayrağına bakan girisliMi; yoksa parseUrl("/giris"). Detay rotasına bağla; giriş sayfasına bayrağı yazan bir buton koy ve iki durumu da dene.
  6. Lazy deneyi: 404 (ya da "hakkında") bileşenini loadComponent'e çevir; Network sekmesinde rotaya ilk girişte inen ayrı .js parç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."