Angular 라우터는 애플리케이션에서 라우팅 규칙이 동작하는 방식을 커스터마이징할 수 있는 강력한 확장 기능을 제공합니다. 기본 라우팅 동작은 대부분의 애플리케이션에 적합하지만, 성능 최적화나 특수한 URL 처리, 복잡한 라우팅 로직이 필요하다면 직접 구현해야 할 수 있습니다.
다음 기능이 필요한 애플리케이션에서 라우팅 규칙 커스터마이징이 유용합니다:
- 데이터를 다시 가져오지 않도록 내비게이션 사이에 컴포넌트 상태 유지하기
- 사용자 동작이나 네트워크 상태에 따른 전략적인 지연 모듈 로드
- 외부 URL 연동 또는 기존 시스템과 Angular 라우팅 규칙 함께 처리하기
- 단순 경로 패턴을 넘어 런타임 조건에 따라 라우팅 규칙을 동적으로 매칭하기
참고: 커스텀 전략을 구현하기 전에 기본 라우터 동작으로 요구사항을 충족할 수 없는지 먼저 확인하세요. Angular의 기본 라우팅은 일반적인 사용 사례에 맞춰 최적화되어 있으며, 성능과 단순성의 균형이 가장 좋습니다. 라우팅 규칙 전략을 커스터마이징하면 코드가 더 복잡해질 수 있으며, 주의해서 관리하지 않으면 메모리 사용량과 성능에도 영향을 줄 수 있습니다.
라우터 설정 옵션
withRouterConfig나 RouterModule.forRoot에 추가 RouterConfigOptions를 전달해서 라우터 동작을 조정할 수 있습니다.
취소된 내비게이션 처리하기
canceledNavigationResolution은 내비게이션이 취소되었을 때 라우터가 브라우저 이력을 복원하는 방식을 제어합니다.
기본값은 'replace'이며, location.replaceState로 내비게이션 이전 URL로 되돌립니다.
따라서 브라우저의 뒤로 또는 앞으로 버튼처럼 내비게이션을 위해 주소 표시줄이 이미 변경된 상황에서 내비게이션이 실패하거나 가드가 거부하면, 이력 항목을 원래 URL로 덮어써서 되돌립니다.
'computed'로 바꾸면 이동 중인 이력 인덱스를 Angular 내비게이션과 동기화합니다.
따라서 뒤로 이동을 취소하면 앞으로 이동을 실행하고, 앞으로 이동을 취소하면 뒤로 이동을 실행해서 원래 페이지로 돌아갑니다.
이 설정은 urlUpdateStrategy: 'eager'를 사용하거나, 브라우저가 시작한 popstate 내비게이션을 가드가 자주 취소하는 경우에 특히 유용합니다.
provideRouter(routes, withRouterConfig({canceledNavigationResolution: 'computed'}));
같은 URL로의 내비게이션 처리하기
onSameUrlNavigation은 사용자가 현재 URL로 이동하려고 할 때의 동작을 설정합니다.
기본값인 'ignore'는 처리를 생략하고, 'reload'는 URL을 건너뛰지 않고 라우터의 내비게이션 처리 과정에서 처리하도록 합니다.
가드와 리졸버를 다시 실행할지는 runGuardsAndResolvers가 결정하고, 컴포넌트를 재사용할지는 라우팅 규칙 재사용 전략이 결정합니다.
URL이 바뀌지 않더라도 목록 필터, 왼쪽 내비게이션 항목, 새로고침 버튼을 반복해서 클릭할 때 데이터를 다시 가져오고 싶은 경우에 유용합니다.
provideRouter(routes, withRouterConfig({onSameUrlNavigation: 'reload'}));
전역 설정 대신 개별 내비게이션에서 이 동작을 제어할 수도 있습니다.
기본값인 'ignore'를 유지하면서 특정 상황에서만 다시 로드하도록 설정할 수 있습니다:
router.navigate(['/some-path'], {onSameUrlNavigation: 'reload'});
TIP: 라우팅 규칙 리소스를 사용하면 onSameUrlNavigation을 설정하거나 라우터 내비게이션을 시작하지 않아도 데이터를 갱신할 수 있습니다. 리소스의 .reload()를 실행하거나 params에서 읽는 시그널을 갱신하세요.
파라미터 상속 제어하기
paramsInheritanceStrategy는 상위 라우팅 규칙의 파라미터와 데이터를 전달하는 방식을 정의합니다.
기본값인 'always'에서는 하위 라우팅 규칙이 상위 라우팅 규칙의 파라미터, 라우팅 규칙 데이터, 리졸버로 가져온 값을 자동으로 상속합니다.
따라서 라우팅 규칙 트리의 더 아래쪽에서도 매트릭스 파라미터, 라우팅 규칙 데이터, 리졸버로 가져온 값을 사용할 수 있습니다.
다음처럼 여러 기능 영역에서 컨텍스트 식별자를 공유할 때 유용합니다:
/org/:orgId/projects/:projectId/customers/:customerId
export const routes: Routes = [
{
path: 'org/:orgId',
component: Organization,
children: [
{
path: 'projects/:projectId',
component: Project,
children: [
{
path: 'customers/:customerId',
component: Customer,
},
],
},
],
},
];
@Component({/* ... */})
export class Customer {
private route = inject(ActivatedRoute);
// 모든 상위 라우팅 규칙 파라미터에 직접 접근할 수 있습니다.
orgId = this.route.snapshot.params['orgId'];
projectId = this.route.snapshot.params['projectId'];
customerId = this.route.snapshot.params['customerId'];
}
이전 동작으로 되돌리려면 paramsInheritanceStrategy를 'emptyOnly'로 설정하세요.
'emptyOnly'에서는 하위 라우팅 규칙의 경로가 비어 있거나 상위 라우팅 규칙에 컴포넌트가 선언되지 않은 경우에만 파라미터를 상속합니다:
provideRouter(routes, withRouterConfig({paramsInheritanceStrategy: 'emptyOnly'}));
이 경우 Customer 컴포넌트는 상위 라우팅 규칙에서 부모 파라미터를 읽어야 합니다:
@Component({/* ... */})
export class Customer {
private route = inject(ActivatedRoute);
orgId = this.route.parent?.parent?.snapshot.params['orgId'];
projectId = this.route.parent?.snapshot.params['projectId'];
customerId = this.route.snapshot.params['customerId'];
}
URL 갱신 시점 정하기
urlUpdateStrategy는 Angular가 브라우저 주소 표시줄을 갱신하는 시점을 결정합니다.
기본값인 'deferred'는 내비게이션이 성공한 후에 URL을 변경합니다.
내비게이션이 시작될 때 즉시 갱신하려면 'eager'를 사용합니다.
즉시 갱신하면 가드나 오류로 내비게이션이 실패했을 때 시도한 URL을 쉽게 표시할 수 있지만, 오래 실행되는 가드가 있다면 진행 중인 URL이 잠시 표시될 수 있습니다.
가드가 이동을 차단하더라도 분석 파이프라인에서 이동을 시도한 라우팅 규칙을 확인해야 한다면 이 설정을 고려해 보세요.
provideRouter(routes, withRouterConfig({urlUpdateStrategy: 'eager'}));
기본 쿼리 파라미터 처리 방식 선택하기
defaultQueryParamsHandling은 Router.createUrlTree를 실행하면서 queryParamsHandling을 지정하지 않았을 때 적용할 기본 동작을 설정합니다.
기본값인 'replace'는 기존 쿼리 문자열을 교체합니다.
'merge'는 전달한 값을 현재 값과 합치고, 'preserve'는 새 값을 명시적으로 전달하지 않는 한 기존 쿼리 파라미터를 유지합니다.
provideRouter(routes, withRouterConfig({defaultQueryParamsHandling: 'merge'}));
검색이나 필터 페이지에서 파라미터를 추가할 때 기존 필터를 자동으로 유지하는 데 특히 유용합니다.
URL 끝의 슬래시 처리하기
Location 서비스는 기본적으로 URL을 읽을 때 끝의 슬래시를 제거합니다.
애플리케이션에 TrailingSlashPathLocationStrategy를 제공하면 Location 서비스가 브라우저에 기록하는 모든 URL 끝에 슬래시를 붙이도록 설정할 수 있습니다.
import {LocationStrategy, TrailingSlashPathLocationStrategy} from '@angular/common';
import {bootstrapApplication} from '@angular/platform-browser';
bootstrapApplication(App, {
providers: [{provide: LocationStrategy, useClass: TrailingSlashPathLocationStrategy}],
});
애플리케이션에 NoTrailingSlashPathLocationStrategy를 제공하면 Location 서비스가 브라우저에 기록하는 모든 URL 끝에 슬래시가 없도록 설정할 수도 있습니다.
import {LocationStrategy, NoTrailingSlashPathLocationStrategy} from '@angular/common';
import {bootstrapApplication} from '@angular/platform-browser';
bootstrapApplication(App, {
providers: [{provide: LocationStrategy, useClass: NoTrailingSlashPathLocationStrategy}],
});
이 전략들은 브라우저에 기록하는 URL에만 영향을 미칩니다.
Location.path()와 Location.normalize()는 URL을 읽을 때 계속 끝의 슬래시를 제거합니다.
Angular 라우터는 크게 네 가지 영역의 커스터마이징을 지원합니다:
라우팅 규칙 재사용 전략
라우팅 규칙 재사용 전략은 내비게이션 중 Angular가 컴포넌트를 종료하고 다시 생성할지, 재사용할 수 있도록 유지할지 제어합니다. 기본적으로 Angular는 라우팅 규칙을 떠날 때 컴포넌트 인스턴스를 종료하고 다시 돌아오면 새 인스턴스를 생성합니다.
라우팅 규칙 재사용을 구현하면 좋은 경우
다음 기능이 필요한 애플리케이션에서 커스텀 라우팅 규칙 재사용 전략이 유용합니다:
- 폼 상태 유지 - 사용자가 다른 페이지로 이동했다가 돌아와도 작성 중인 폼을 유지합니다.
- 비용이 큰 데이터 유지 - 대규모 데이터를 다시 가져오거나 복잡한 계산을 반복하지 않도록 합니다.
- 스크롤 위치 유지 - 긴 목록이나 무한 스크롤 화면에서 스크롤 위치를 유지합니다.
- 탭 형태의 인터페이스 - 탭을 전환할 때 컴포넌트 상태를 유지합니다.
커스텀 라우팅 규칙 재사용 전략 만들기
Angular의 RouteReuseStrategy 클래스를 사용하면 "분리된 라우팅 규칙 핸들(detached route handle)"이라는 개념으로 내비게이션 동작을 커스터마이징할 수 있습니다.
분리된 라우팅 규칙 핸들은 Angular가 컴포넌트 인스턴스와 전체 뷰 계층을 저장하는 방식입니다. 라우팅 규칙을 분리하면 Angular는 컴포넌트 인스턴스, 하위 컴포넌트, 관련된 모든 상태를 메모리에 유지합니다. 나중에 해당 라우팅 규칙으로 다시 이동할 때 이 상태를 다시 연결할 수 있습니다.
RouteReuseStrategy 클래스는 라우팅 규칙 컴포넌트의 라이프사이클을 제어하는 다음 메서드를 제공합니다:
| 메서드 | 설명 |
|---|---|
shouldDetach |
라우팅 규칙을 떠날 때 이후 재사용을 위해 저장할지 결정합니다. |
store |
shouldDetach가 true를 반환하면 분리된 라우팅 규칙 핸들을 저장합니다. |
shouldAttach |
라우팅 규칙으로 이동할 때 저장된 라우팅 규칙을 다시 연결할지 결정합니다. |
retrieve |
다시 연결할 수 있도록 이전에 저장한 라우팅 규칙 핸들을 반환합니다. |
shouldReuseRoute |
내비게이션 중 현재 라우팅 규칙 인스턴스를 종료하지 않고 재사용할지 결정합니다. |
shouldDestroyInjector |
분리된 라우팅 규칙이 더 이상 저장되지 않을 때 해당 인젝터를 종료할지 결정합니다. |
다음 예제는 라우팅 규칙 메타데이터에 따라 컴포넌트 상태를 선택적으로 유지하는 커스텀 라우팅 규칙 재사용 전략입니다:
import {
RouteReuseStrategy,
Route,
ActivatedRouteSnapshot,
DetachedRouteHandle,
} from '@angular/router';
import {Injectable} from '@angular/core';
@Injectable()
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
private handlers = new Map<Route | null, DetachedRouteHandle>();
shouldDetach(route: ActivatedRouteSnapshot): boolean {
// 이후 재사용을 위해 라우팅 규칙을 저장할지 결정합니다.
return route.data['reuse'] === true;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
// shouldDetach가 true를 반환하면 분리된 라우팅 규칙 핸들을 저장합니다.
if (handle && route.data['reuse'] === true) {
const key = this.getRouteKey(route);
this.handlers.set(key, handle);
}
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
// 저장된 라우팅 규칙을 다시 연결할지 확인합니다.
const key = this.getRouteKey(route);
return route.data['reuse'] === true && this.handlers.has(key);
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
// 다시 연결할 수 있도록 저장된 라우팅 규칙 핸들을 반환합니다.
const key = this.getRouteKey(route);
return route.data['reuse'] === true ? (this.handlers.get(key) ?? null) : null;
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
// 현재 라우팅 규칙 인스턴스를 재사용할지 결정합니다.
return future.routeConfig === curr.routeConfig;
}
private getRouteKey(route: ActivatedRouteSnapshot): Route | null {
return route.routeConfig;
}
}
분리된 라우팅 규칙 핸들을 수동으로 종료하기
커스텀 RouteReuseStrategy를 구현할 때 DetachedRouteHandle을 다시 연결하지 않고 버리기로 했다면 수동으로 종료해야 할 수 있습니다.
예를 들어 캐시 크기를 제한하거나 일정 시간이 지난 핸들을 만료시키는 전략이라면, 메모리 누수를 방지하기 위해 컴포넌트와 상태를 적절하게 정리해야 합니다.
DetachedRouteHandle은 내부 구조가 공개되지 않는 불투명 타입이므로 종료 메서드를 직접 실행할 수 없습니다.
대신 라우터가 제공하는 destroyDetachedRouteHandle 함수를 사용합니다.
import {destroyDetachedRouteHandle} from '@angular/router';
// ... 전략 내부
if (this.handles.size > MAX_CACHE_SIZE) {
const handle = this.handles.get(oldestKey);
if (handle) {
destroyDetachedRouteHandle(handle);
this.handles.delete(oldestKey);
}
}
참고: canMatch 가드를 사용하는 경우에는 중복 항목이 생길 수 있으므로 라우팅 규칙 경로를 키로 사용하지 않는 것이 좋습니다.
사용하지 않는 라우팅 규칙 인젝터 자동 정리하기
기본적으로 Angular는 RouteReuseStrategy가 더 이상 저장하지 않는 분리된 라우팅 규칙이라도 해당 인젝터를 종료하지 않습니다.
대부분의 애플리케이션에서는 이 수준의 메모리 관리가 필요하지 않기 때문입니다.
사용하지 않는 라우팅 규칙 인젝터를 자동으로 정리하려면 라우터 설정에서 withAutoCleanupInjectors 기능을 사용하면 됩니다.
이 기능은 내비게이션 후에 전략이 현재 저장 중인 라우팅 규칙을 확인하고, 재사용 전략에 저장되지 않은 분리된 라우팅 규칙의 인젝터를 종료합니다.
import {provideRouter, withAutoCleanupInjectors} from '@angular/router';
import {ApplicationConfig} from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes, withAutoCleanupInjectors())],
};
커스텀 RouteReuseStrategy를 제공하지 않았거나 커스텀 전략이 BaseRouteReuseStrategy를 상속한다면, 이제 라우팅 규칙이 비활성화될 때 인젝터가 종료됩니다.
커스텀 RouteReuseStrategy에서 정리하기
커스텀 RouteReuseStrategy를 사용하면서 해당 전략이 BaseRouteReuseStrategy를 상속하지 않는다면, shouldDestroyInjector를 구현해서 어떤 라우팅 규칙의 인젝터를 종료해야 하는지 알려줘야 합니다:
@Injectable()
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
// ... 다른 메서드
shouldDestroyInjector(route: Route): boolean {
return !route.data['retainInjector'];
}
}
전략에서 DetachedRouteHandle을 저장한다면, 분리된 핸들에 필요한 인젝터를 종료하지 않도록 저장한 핸들을 라우터에 알려줘야 합니다:
@Injectable()
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
private readonly handles = new Map<Route, DetachedRouteHandle>();
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null) {
if (handle) {
this.handles.set(route.routeConfig!, handle);
}
}
retrieveStoredRouteHandles(): DetachedRouteHandle[] {
return Array.from(this.handles.values());
}
// ... 다른 메서드
}
라우팅 규칙에 커스텀 재사용 전략 적용하기
라우팅 규칙 설정의 메타데이터로 재사용 동작을 선택할 수 있습니다. 이 방식은 재사용 로직을 컴포넌트 코드와 분리하므로, 컴포넌트를 수정하지 않고 동작을 쉽게 조정할 수 있습니다:
export const routes: Routes = [
{
path: 'products',
component: ProductList,
data: {reuse: true}, // 내비게이션 사이에 컴포넌트 상태를 유지합니다.
},
{
path: 'products/:id',
component: ProductDetail,
// 재사용 플래그가 없으므로 내비게이션마다 컴포넌트를 다시 생성합니다.
},
{
path: 'search',
component: Search,
data: {reuse: true}, // 검색 결과와 필터 상태를 유지합니다.
},
];
Angular의 의존성 주입 시스템으로 애플리케이션 수준에서 커스텀 라우팅 규칙 재사용 전략을 설정할 수도 있습니다. 이 경우 Angular는 전략 인스턴스를 하나 생성하고, 이 인스턴스가 애플리케이션 전체에서 라우팅 규칙 재사용 여부를 결정합니다:
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
{provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy},
],
};
미리 로드 전략
미리 로드 전략은 Angular가 지연 로드 대상인 라우팅 규칙 모듈을 언제 백그라운드에서 불러올지 결정합니다. 지연 로드는 모듈 다운로드를 미뤄서 초기 로드 시간을 줄이지만, 사용자가 해당 라우팅 규칙으로 처음 이동할 때는 여전히 지연이 발생합니다. 미리 로드 전략은 사용자가 요청하기 전에 모듈을 불러와 이 지연을 없앱니다.
내장 미리 로드 전략
Angular는 두 가지 미리 로드 전략을 기본으로 제공합니다:
| 전략 | 설명 |
|---|---|
NoPreloading |
모든 미리 로드를 비활성화하는 기본 전략입니다. 사용자가 해당 라우팅 규칙으로 이동할 때만 모듈을 로드합니다. |
PreloadAllModules |
최초 내비게이션 직후 모든 지연 로드 모듈을 불러옵니다. |
PreloadAllModules 전략은 다음과 같이 설정할 수 있습니다:
import {ApplicationConfig} from '@angular/core';
import {provideRouter, withPreloading, PreloadAllModules} from '@angular/router';
import {routes} from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes, withPreloading(PreloadAllModules))],
};
PreloadAllModules 전략은 모든 모듈을 다운로드해도 성능에 큰 영향을 주지 않는 중소 규모 애플리케이션에 적합합니다.
기능 모듈이 많은 대규모 애플리케이션에서는 더 선택적인 미리 로드 방식이 유리할 수 있습니다.
커스텀 미리 로드 전략 만들기
커스텀 미리 로드 전략은 preload 메서드 하나가 필요한 PreloadingStrategy 인터페이스를 구현합니다.
이 메서드는 라우팅 규칙 설정과 실제 모듈 로드를 실행하는 함수를 인자로 받습니다.
미리 로드가 완료될 때 값을 전달하는 옵저버블을 반환하거나, 미리 로드를 생략하려면 빈 옵저버블을 반환합니다:
import {Injectable} from '@angular/core';
import {PreloadingStrategy, Route} from '@angular/router';
import {Observable, of, timer} from 'rxjs';
import {mergeMap} from 'rxjs/operators';
@Injectable()
export class SelectivePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// data: { preload: true }가 지정된 라우팅 규칙만 미리 로드합니다.
if (route.data?.['preload']) {
return load();
}
return of(null);
}
}
이 선택적 전략은 라우팅 규칙 메타데이터를 확인해서 미리 로드할지 결정합니다. 라우팅 규칙 설정에서 미리 로드를 사용하도록 지정할 수 있습니다:
import {Routes} from '@angular/router';
export const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.routes'),
data: {preload: true}, // 최초 내비게이션 직후 미리 로드합니다.
},
{
path: 'reports',
loadChildren: () => import('./reports/reports.routes'),
data: {preload: false}, // 사용자가 reports로 이동할 때만 로드합니다.
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes'),
// 미리 로드 플래그가 없으므로 미리 로드하지 않습니다.
},
];
미리 로드의 성능 고려사항
미리 로드는 네트워크 사용량과 메모리 소비량에 모두 영향을 미칩니다. 미리 불러온 각 모듈은 대역폭을 사용하고 애플리케이션의 메모리 점유량을 늘립니다. 데이터 사용량에 따라 요금이 부과되는 연결을 사용하는 모바일 사용자에게는 최소한의 미리 로드가 적합할 수 있지만, 빠른 네트워크를 사용하는 데스크톱 사용자에게는 적극적인 미리 로드 전략을 적용할 수 있습니다.
미리 로드하는 시점도 중요합니다. 초기 로드 직후 미리 로드를 시작하면 이미지나 API 호출 같은 중요한 리소스와 경쟁할 수 있습니다. 전략을 설계할 때는 로드 후 애플리케이션 동작을 고려하고 다른 백그라운드 작업과 조율해서 성능 저하를 피해야 합니다.
브라우저의 리소스 제한도 미리 로드에 영향을 미칩니다. 브라우저는 동시 HTTP 연결 수를 제한하므로, 미리 로드를 과도하게 실행하면 다른 요청 뒤에서 대기할 수 있습니다. 서비스 워커로 캐싱과 네트워크 요청을 세밀하게 제어하면 미리 로드 전략을 보완할 수 있습니다.
URL 처리 전략
URL 처리 전략은 Angular 라우터가 처리할 URL과 무시할 URL을 결정합니다. 기본적으로 Angular는 애플리케이션 내부의 모든 내비게이션 이벤트를 처리하려고 합니다. 하지만 실제 애플리케이션은 다른 시스템과 공존하거나, 외부 링크를 처리하거나, 자체 라우팅 규칙을 관리하는 기존 애플리케이션과 연동해야 할 때가 많습니다.
UrlHandlingStrategy 클래스를 사용하면 Angular가 관리하는 라우팅 규칙과 외부 URL 사이의 경계를 제어할 수 있습니다.
애플리케이션을 Angular로 점진적으로 전환하거나 다른 프레임워크와 URL 영역을 공유해야 할 때 필수적입니다.
커스텀 URL 처리 전략 구현하기
커스텀 URL 처리 전략은 UrlHandlingStrategy 클래스를 상속하고 세 가지 메서드를 구현합니다.
shouldProcessUrl은 Angular가 해당 URL을 처리할지 결정하고, extract는 Angular가 처리할 URL 부분을 반환하며, merge는 이 URL 부분을 나머지 URL과 합칩니다:
import {Injectable} from '@angular/core';
import {UrlHandlingStrategy, UrlTree} from '@angular/router';
@Injectable()
export class CustomUrlHandlingStrategy implements UrlHandlingStrategy {
shouldProcessUrl(url: UrlTree): boolean {
// /app 또는 /admin으로 시작하는 URL만 처리합니다.
return url.toString().startsWith('/app') || url.toString().startsWith('/admin');
}
extract(url: UrlTree): UrlTree {
// 처리할 URL을 변경 없이 반환합니다.
return url;
}
merge(newUrlPart: UrlTree, rawUrl: UrlTree): UrlTree {
// URL 부분을 나머지 URL과 합칩니다.
return newUrlPart;
}
}
이 전략은 URL 영역을 명확하게 구분합니다.
Angular는 /app과 /admin 경로를 처리하고 나머지는 무시합니다.
이 패턴은 Angular가 특정 영역을 맡고 기존 시스템이 나머지를 유지하는 점진적 전환에 적합합니다.
커스텀 URL 처리 전략 설정하기
Angular의 의존성 주입 시스템으로 커스텀 전략을 등록할 수 있습니다:
import {ApplicationConfig} from '@angular/core';
import {provideRouter, UrlHandlingStrategy} from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
{provide: UrlHandlingStrategy, useClass: CustomUrlHandlingStrategy},
],
};
커스텀 라우팅 규칙 매처
기본적으로 Angular 라우터는 정의된 순서대로 라우팅 규칙을 살펴보면서 URL 경로를 각 라우팅 규칙의 경로 패턴과 비교합니다.
정적 세그먼트, 파라미터가 있는 세그먼트(:id), 와일드카드(**)를 지원합니다.
처음 일치하는 라우팅 규칙을 선택하면 탐색을 멈춥니다.
런타임 조건, 복잡한 URL 패턴, 다른 커스텀 규칙을 바탕으로 더 정교한 매칭 로직이 필요하다면 커스텀 매처를 사용할 수 있습니다. 기본 라우팅 규칙의 단순함을 유지하면서 이런 유연성을 더할 수 있습니다.
라우터는 라우팅 규칙 매칭 단계에서 경로를 비교하기 전에 커스텀 매처를 실행합니다. 매처가 일치 결과를 반환할 때 URL에서 파라미터도 추출할 수 있으며, 활성화된 컴포넌트는 일반 라우팅 규칙 파라미터처럼 이 값을 사용할 수 있습니다.
커스텀 매처 만들기
커스텀 매처는 URL 세그먼트를 받아 처리한 세그먼트와 파라미터를 담은 매칭 결과를 반환하거나, 일치하지 않으면 null을 반환하는 함수입니다. Angular가 라우팅 규칙의 path 프로퍼티를 검사하기 전에 매처 함수가 실행됩니다:
import {Route, UrlSegment, UrlSegmentGroup, UrlMatchResult} from '@angular/router';
export function customMatcher(
segments: UrlSegment[],
group: UrlSegmentGroup,
route: Route,
): UrlMatchResult | null {
// 이곳에 매칭 로직을 작성합니다.
if (matchSuccessful) {
return {
consumed: segments,
posParams: {
paramName: new UrlSegment('paramValue', {}),
},
};
}
return null;
}
버전 기반 라우팅 구현하기
URL의 버전 번호에 따라 라우팅해야 하는 API 문서 사이트를 생각해 봅시다. 버전마다 컴포넌트 구조나 제공하는 기능이 다를 수 있습니다:
import {Routes, UrlSegment, UrlMatchResult} from '@angular/router';
export function versionMatcher(segments: UrlSegment[]): UrlMatchResult | null {
// /v1/docs, /v2.1/docs, /v3.0.1/docs 같은 패턴을 매칭합니다.
if (segments.length >= 2 && segments[0].path.match(/^v\d+(\.\d+)*$/)) {
return {
consumed: segments.slice(0, 2), // 버전과 'docs' 세그먼트를 처리합니다.
posParams: {
version: segments[0], // 버전을 파라미터로 제공합니다.
section: segments[1], // 섹션도 파라미터로 제공합니다.
},
};
}
return null;
}
// 라우팅 규칙 설정
export const routes: Routes = [
{
matcher: versionMatcher,
component: Documentation,
},
{
path: 'latest/docs',
redirectTo: 'v3/docs',
},
];
컴포넌트는 라우팅 규칙 입력을 통해 추출한 파라미터를 전달받습니다:
import {Component, input, inject, resource} from '@angular/core';
@Component({
selector: 'app-documentation',
template: `
@if (documentation.isLoading()) {
<div>Loading documentation...</div>
} @else if (documentation.error()) {
<div>Error loading documentation</div>
} @else if (documentation.value(); as docs) {
<article>{{ docs.content }}</article>
}
`,
})
export class Documentation {
// 라우팅 규칙 파라미터는 입력 시그널에 자동으로 바인딩됩니다.
version = input.required<string>(); // 버전 파라미터를 받습니다.
section = input.required<string>(); // 섹션 파라미터를 받습니다.
private docsService = inject(DocumentationService);
// 버전이나 섹션이 바뀌면 리소스가 문서를 자동으로 불러옵니다.
documentation = resource({
params: () => {
if (!this.version() || !this.section()) return;
return {
version: this.version(),
section: this.section(),
};
},
loader: ({params}) => {
return this.docsService.loadDocumentation(params.version, params.section);
},
});
}
지역에 따른 라우팅
여러 국가를 지원하는 애플리케이션은 URL에 지역 정보를 포함하는 경우가 많습니다. 커스텀 매처로 지역 코드를 추출해서 알맞은 컴포넌트로 연결하고, 지역을 파라미터로 제공할 수 있습니다:
// 지원하는 로케일
const locales = ['en', 'es', 'fr', 'de', 'ja', 'zh'];
export function localeMatcher(segments: UrlSegment[]): UrlMatchResult | null {
if (segments.length > 0) {
const potentialLocale = segments[0].path;
if (locales.includes(potentialLocale)) {
// 로케일 접두사를 처리하고 계속 매칭합니다.
return {
consumed: [segments[0]],
posParams: {
locale: segments[0],
},
};
} else {
// 로케일 접두사가 없으면 기본 로케일을 사용합니다.
return {
consumed: [], // 어떤 세그먼트도 처리하지 않습니다.
posParams: {
locale: new UrlSegment('en', {}),
},
};
}
}
return null;
}
복잡한 비즈니스 로직으로 매칭하기
커스텀 매처는 경로 패턴으로 표현하기 어려운 비즈니스 규칙을 구현하는 데 유용합니다. 상품 종류에 따라 상품 URL 패턴이 달라지는 전자상거래 사이트를 생각해 봅시다:
export function productMatcher(segments: UrlSegment[]): UrlMatchResult | null {
if (segments.length === 0) return null;
const firstSegment = segments[0].path;
// 도서: /isbn-1234567890
if (firstSegment.startsWith('isbn-')) {
return {
consumed: [segments[0]],
posParams: {
productType: new UrlSegment('book', {}),
identifier: new UrlSegment(firstSegment.substring(5), {}),
},
};
}
// 전자제품: /sku/ABC123
if (firstSegment === 'sku' && segments.length > 1) {
return {
consumed: segments.slice(0, 2),
posParams: {
productType: new UrlSegment('electronics', {}),
identifier: segments[1],
},
};
}
// 의류: /style/BRAND/ITEM
if (firstSegment === 'style' && segments.length > 2) {
return {
consumed: segments.slice(0, 3),
posParams: {
productType: new UrlSegment('clothing', {}),
brand: segments[1],
identifier: segments[2],
},
};
}
return null;
}
커스텀 매처의 성능 고려사항
커스텀 매처는 내비게이션을 시도할 때마다 일치하는 결과를 찾을 때까지 실행됩니다. 따라서 라우팅 규칙이 많은 애플리케이션에서는 특히 복잡한 매칭 로직이 내비게이션 성능에 영향을 줄 수 있습니다. 매처의 역할을 명확히 하고 효율적으로 작성하세요:
- 일치할 수 없는 경우에는 즉시 반환합니다.
- API 호출이나 복잡한 정규 표현식처럼 비용이 큰 작업은 피합니다.
- 반복되는 URL 패턴의 결과를 캐싱하는 것을 고려합니다.
커스텀 매처로 복잡한 라우팅 요구사항을 간결하게 해결할 수 있지만, 지나치게 사용하면 라우팅 규칙 설정을 이해하고 유지보수하기 어려워집니다. 기본 경로 매칭으로 해결하기 어려운 경우에만 사용하세요.