Angular’da Routing İşlemleri
Angular, modern SPA(single page applicaiton) web uygulamaları geliştirmek için popüler bir platformdur. Bu platformun temel yapı taşlarından biri de component’lerdir. Angular component’leri, kullanıcı arayüzünü(view) oluşturmak için kullanılır. Angular uygulamları SPA oldukları için, sayfalar arasında geçiş yoktur. View’ler arasında geçiş vardır. Yani bütün uygulama tek bir sayfada render edilir. Angular’ın sahip olduğu Routing mekanizması sayesinde bu View’ler arasında geçişler yapılır.
Bir SPA uygulamasında, kullanıcılar farklı View’lere giderek işlmelerini yaparlar. Bu geçişler içinde Angular’da bu işi Router üstlenmektedir. Router geçişleri sağlarken tarayıcılardaki URL’leride değiştirerek sanki başka sayfaya yönlendirme izlenimi verir.
Angular Uygulamasında Route Oluşturma:
Angular uygulamasında route oluşturmanın 3 temel adımı vardır.
1) Route’ları “app.config.ts” içerisine import edip, “providerRouter” metodu içerisine vermek.
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};
2) İkinci adımda “app.config.ts” dosyası içerisinde route’ları tanımlamak. Route’ları tanımlarken “path” ve “component” parametreleri verilir. “path” o route için “URL ”bilgisini tutarken, “component” ise Angular component bilgisini tutar.
import { Routes } from '@angular/router';
import { ParentComponent } from './parent/parent.component';
export const routes: Routes = [
{
path: 'parent',
component: ParentComponent
}
];
3) Şuan route’lar oluşturuldu ve uygulamaya eklendi. Son adımda oluşturulan route’ların kullanımı kaldı. Angular’da template içerisinde route kullanımı “routerLink” attribute ile sağlanır. “routerLink ”attribute’ü genellikle <a></a> ile kullanılır. Ayrıca “<router — outlet>” diretive’ide component içerisinde kullanılır. Bu directive, gelen route bilgisine göre verilen component’in render edildiği yeri gösterir.
<h1>Angular Router App</h1>
<nav>
<ul>
<li><a routerLink="/parent" routerLinkActive="active" ariaCurrentWhenActive="page">Parent Component</a></li>
</ul>
</nav>
<!-- The routed views render in the <router-outlet>-->
<router-outlet></router-outlet>
Önemli: Eğer standalone bir component içerisinde kullanıyorsak ilgili directive’leri aşağıdaki gibi component’e import etmeyi unutmayalım.
@Component({
selector: 'app-root',
standalone: true,
// import kısmı önemli
imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'routing-app';
}
Angular uygulamalarında basitçe bu şekilde route’lar oluşturuluyor. Uygulamanın ihtiyacına göre gerekli route mekanizmaları oluşturulabilir.
Route’larda Sıralama:
Route’larda sıralama önemlidir çünkü “Router” ilk eşleşen route kazanır mantığıyla çalışıyor. O yüzden daha belirgin ve açık olan route’lar daha yukarıda tanımlanmalıdır. Yani belli path’leri olan route’ları ilk önce tanımlamalıyız. Ardından boş olanlar veya varsayılanlar. En sona da Wildcard route’lar tanımlanmalıdır. Çünkü Wildcard route’lar her kombinasyonu karşılamaktadırlar.
Wildcard Route Oluşturma:
Güzel kurgulanmış bir uygulama kullanıcının bütün taleplerine karşılık verendir. Kullanıcı uygulamanın olmayan bir kısmına gitmek istediğinde, uygulama bu kısmın olmadığını kullanıcıya vermelidir. Yani kısacası kullanıcı olmayan bir URL’e gittiğinde 404 veya başka özel sayfalara yönlendirilmelidir.
Wildcard route eklemek için “routes” dizisine { path: ‘**’, component: } ile bir varsayılan route eklenmelidir.
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
// Wildcard route for a 404 page
{ path: '**', component: PageNotFoundComponent },
];
Son eklenen route, “**” ile tanımlanmıştır. Buda herhangi bir route eşleşmediğinde bu route çağrılacaktır.
Route İle Parametre Gönderimi:
Kullanıcılar uygulamada gezinirken ve bir route ile başka View’e geçiş yaparken bazen bir bilgi gönderme ihtiyacı duyulabiliyor. Örneğin, Bir alışveriş listesi gösteriliyor olsun. Bu listede herhangi bir ürüne tıklanıldığında açılacak View’de de o ürünün detayı gösteriliyor olsun. İşte bu gidişte bu ürünün id değerine ihtiyaç vardır. Bu id değeri Angular Router sayesinde gönderilebiliyor.
Bunu yapmak için “app.config.ts” içerisinde “provideRouter” fonksiyonuna route’ler verilirken, ikinci parametre olarak withComponentInputBinding verilmelidir.
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes, withComponentInputBinding())]
};
Daha sonra route tanımı yapılırken, route’un parametre beklediği belirtilir.
const routes: Routes = [
{
path: "search/:id", // id bekler
component: SearchComponent,
}
]
Dolayısıyla “http://localhost:4200/search/2” gibi bir URL geldiği zaman, route tanımı, 2'nin id’ye karşılık geldğini çözecektir. Ayrıca route’un yönlendirme yaptığı component içerisinde bir tane “@Intput” data-bound-property ile gönderilen id bilgisi yakalanmaktadır.
@Input()
set id(productId: string) {
this.product$ = this.service.getProduct(productId);
}
Önemli: Route içerisinde tanımlanan bütün keylerin değerleri static veyahut resolved olarak component’in Input’larına bind edilebilir. Eğer parent component route bilgilerininde kullanılması isteniyorsa aşağıdaki gibi
withRouterConfig({paramsInheritanceStrategy: 'always'})
bu ayar ‘always’ olarak verilmelidir.
import { provideRouter, withRouterConfig } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes, withRouterConfig({
paramsInheritanceStrategy: 'always' // bu kısım eklenmelidir.
}))]
};
Bu ayar verildiğinde route içerisinde bulunan bütün parametreler ilgili componentlerin input’larına bind edilir. Örneğin şöyle bir route tanımı yapıldığını varsayalım.
export const routes: Routes = [
{
path: 'parent',
component: ParentComponent
},
{
path: 'company/:companyId',
component: CompanyComponent,
children: [
{
path: 'year/:year',
component: YearComponent,
children: [
path: 'customers',
component: CustomersComponent
]
}
]
},
];
Yukarıdaki tanıma göre şöyle bir adres https://localhost:4200/company/7/year/2022/customers geldğinde, Company component’e 7 ve Year component’e de 2022 bind edilir.
Route Redirect Kullanımı:
Bazen bir route’un başka bir route’a yönlendirme yapması gerekebilir. Angular Router içerisinde bu işlem “path”, “component” , “redirectTo” ve “pathMatch” ile yapılır. Burada “pathMatch” yönlendirmenin nasıl yapacağını belirtir.
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
// redirect to `first-component`
{ path: '', redirectTo: '/first-component', pathMatch: 'full' },
// Wildcard route for a 404 page
{ path: '**', component: PageNotFoundComponent },
];
Bazen yönlendirme yukarıdaki örnekteki gibi basit ve statik olmayabilir. İşlemi bir fonksiyon ile daha karmaşık işlemler sonucu yönlendirme yapılmak istenebilir. Örneğin:
const routes: Routes = [
{ path: "first-component", component: FirstComponent },
{
path: "old-user-page",
redirectTo: ({ queryParams }) => {
const errorHandler = inject(ErrorHandler);
const userIdParam = queryParams['userId'];
if (userIdParam !== undefined) {
return `/user/${userIdParam}`;
} else {
errorHandler.handleError(new Error('Attempted navigation to user page without user ID.'));
return `/not-found`;
}
},
},
{ path: "user/:userId", component: OtherComponent },
];
Yukarıdaki örnekte gelen userId parametresine göre yönlendirme yapılmaktadır.
Nested Route Kullanımı:
Bazen bir component içerisinde kurguladığı route mekanizması ile sahip olduğu child component’ler gösterilmek istenebilir. Bunun için ilgili component’e ikinci bir “<router-outlet>” eklenir. Örnek ile daha iyi anlaşılacaktır.
First component içerisinde tanımlı bir “<nav></nav>” ile iki tane child component’in route’ları eklenmiştir. Daha sonra bu iki child component’ler <router-outlet> ile render edilmiştir.
<h2>First Component</h2>
<nav>
<ul>
<li><a routerLink="child-a">Child A</a></li>
<li><a routerLink="child-b">Child B</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
Firtst component route’ı içerisinde child component’lerin route’ları “children” ile tanımlanmıştır.
const routes: Routes = [
{
path: 'first-component',
// this is the component with the <router-outlet> in the template
component: FirstComponent,
children: [
{
// child route path
path: 'child-a',
// child route component that the router renders
component: ChildAComponent,
},
{
path: 'child-b',
// another child route component that the router renders
component: ChildBComponent,
},
],
},
];
Route İle Sayfa Başlığı Ayarlama:
Her sayfa, tarayıcının geçmişinde tutulması için benzersiz bir başlığa sahip olmalıdır. Router View’ler için sayfa başlığını “title” parametresi ile sağlar.
const routes: Routes = [
{
path: 'first-component',
title: 'First component',
// this is the component with the <router-outlet> in the template
component: FirstComponent,
children: [
{
path: 'child-a',
title: resolvedChildATitle, // resolveFn function for title
component: ChildAComponent,
},
{
path: 'child-b',
title: 'child b', // title
component: ChildBComponent,
},
],
},
];
// ResolveFn Resolver
const resolvedChildATitle: ResolveFn<string> = () => Promise.resolve('child a');
Önemli: title property’si aynı route’lar gibi static verilebildiği gibi dinamik olarak “ResolveFn” fonksiyonu ile de ayarlabilir.
Ayrıca bütün sayfa başlıkları için özel başlık stratejiside uygulanabilir. Bunu yapmak için “TitleStrategy ” interface’ini extend eden bir servis yazılmalıdır. Daha sonrada bu servis “app.config.ts” içerisine verilmelidir.
Servis aşağıdaki gibidir.
import { Injectable } from '@angular/core';
import { TitleStrategy } from '@angular/router';
@Injectable({providedIn: 'root'})
export class TemplatePageTitleStrategy extends TitleStrategy { // extends TitleStrategy
constructor(private readonly title: Title) {
super();
}
override updateTitle(routerState: RouterStateSnapshot) {
const title = this.buildTitle(routerState);
if (title !== undefined) {
this.title.setTitle(`My Application | ${title}`);
}
}
}
Daha sonra bu servis “app.config.ts” dosyasında strateji olarak ayarlanmalıdır.
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
// useClass TemplatePageTitleStrategy
{provide: TitleStrategy, useClass: TemplatePageTitleStrategy},
]
};
Relative Path Kullanımı:
Relative path mevcut URL’e göre yeni bir URL oluşturma işlemidir. Route ağacında aynı seviyede bulunan iki View arasında “../” notasyonu ile relative olarak geçiş yapılabilir. Aşağıdaki örnekte “First Component” altındaki “second-component” route’i relative olarak verilmiştir.
<h2>First Component</h2>
<nav>
<ul>
<li><a routerLink="../second-component">Relative Route to second component</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
Component sınıfının içinden yapılan yönlendirmelerde ise “NavigationExtras” nın “relativeTo” property’sine “ActivatedRoute” verilerek yapılır.
import { Component} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-first',
templateUrl: './first.component.html',
styleUrl: './first.component.css'
})
export class FirstComponent {
constructor(
private router: Router,
private route: ActivatedRoute)
{ }
goToItems() {
// relativeTo as ActivatedRoute
this.router.navigate(['items'], { relativeTo: this.route });
}
}
Route Query Parametrelerine Erişim:
Bazen uygulamada, component içerisinde route’un diğer parçaları olan parametrelere erişmek istenebilir. Bunun için öncelikle aşağıdaki elemanların component sınıfına import edilmesi gerekmektedir.
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
Sonra ActivatedRoute component içerisinde inject edilir.
constructor(private route: ActivatedRoute) {}
Daha sonra component’in “ngOnInit()” metodunda ActivatedRoute olan route’un paramMap içinden id si alınmaktadır.
heroes$: Observable<Hero[]>;
selectedId: number;
heroes = HEROES;
ngOnInit() {
this.heroes$ = this.route.paramMap.pipe(
switchMap(params => {
this.selectedId = Number(params.get('id'));
return this.service.getHeroes();
})
);
}
yukarıda işin içinde biraz RxJS kullanılmıştır. Aşağıdaki örnek biraz daha sade ve anlaşılır bir örnektir.
hero$: Observable<Hero>;
constructor(
private route: ActivatedRoute,
private router: Router ) {}
ngOnInit() {
// id elde ediliyor.
const heroId = this.route.snapshot.paramMap.get('id');
this.hero$ = this.service.getHero(heroId);
}
gotoItems(hero: Hero) {
const heroId = hero ? hero.id : null;
// Pass along the hero id if available
// so that the HeroList component can select that item.
this.router.navigate(['/heroes', { id: heroId }]);
}
Lazy Loading ve Guard Kullanımı:
Angular’da Lazy Loading , uygulama modüllere ayrıldığı zaman sadece kullanılan modüllerin yüklenmesi olayına Lazy Loading denir. Peki uygulama hangi modülün kullanıldığını nereden anlıyor? İşte bu sorunun cevabı route’dur. Çünkü route belli bir component’i gösterdiği gibi bir mödülü ve o modülün bütün alt component’lerini de gösterebilir.
Angular’da route’ların yetki kontrolleri Guard dediğimiz yapılar ile sağlanır. Guard’ların çeştli kullanım senaryolarına göre sahip oldukları metotlar vardır. Bunlar: “canActivate”, “canActivateChild”, “canDeactivate”, ”canMatch”, ”resolve” ve ”canLoad” metotlarıdır.
Lazy Loading ve Guard ile ilgili şimdilik bu kadar bir giriş yapmak istedim. Çünkü performansı etkileyen ve önemli birer özelliktir. Başka bir makalede ayrıntılı bir şekilde açıklamayı düşünüyorum.
Çok uzun bir yazı oldu farkındayım. Fakat konular bir bütünlük içinde olduğu için hepsine değinmek istedim. Aslında Angular bu route konusunda baya bir özellik ve esneklik sağlamış durumda. Bunu da görmüş olduk. Bu yazıda route’ların olışturulması, sıralanması, wilcard route’ların oluşturulması, parametre gönderme ve alma, sayfa başlığı tanımlama ve relative route oluşturma gibi route’lar ile alakalı hemen herşeye değindiğimiz çok zengin bir içerik oldu. Umarım faydalı olur.
Yazıyı, yeni başlayanlar veya İngilizcesi yeterli olmayanlar için mümkün olduğunca anlaşılır bir şekilde yazmaya çalıştım. Umarım faydalı olmuştur. Aklınıza takılan herhangi bir soruyu çekinmeden yorumlarda sorabilirsiniz.
Yaralanılan Kaynaklar: