앵귤러 기초 강좌 #6 Router 기초
지난 시간에는 Service와 의존성 주입을 다뤘습니다. 지금까지는 화면 하나 안에서 컴포넌트와 데이터를 주고받는 일들을 살펴봤는데, 실제 앱은 보통 여러 화면을 갖습니다. 메뉴를 클릭하면 화면이 바뀌고, URL이 바뀌고, 뒤로 가기 버튼도 동작해야 합니다. 이런 화면 전환을 다루는 도구가 앵귤러의 Router입니다.
리액트가 React Router를 외부 라이브러리로 따로 가져다 쓰는 것과 달리, 앵귤러는 풀패키지 프레임워크답게 Router를 **공식 패키지(@angular/router)**로 한 세트에 담아 제공합니다. 새 프로젝트를 ng new 할 때 “Would you like to add Angular routing?“이라고 물어보는 그 친구입니다.
Router 셋업 #
모던 앵귤러(v17+)는 standalone 패턴을 기본으로 합니다. Router 역시 NgModule 없이 provideRouter 함수 하나로 셋업합니다.
import { Routes } from '@angular/router';
import { HomeComponent } from './pages/home.component';
import { AboutComponent } from './pages/about.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
];import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
],
};import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig);ng new로 만든 프로젝트라면 이 구조가 이미 갖춰져 있습니다. 우리가 손댈 곳은 보통 app.routes.ts 한 곳뿐입니다.
RouterModule.forRoot(routes)를 imports에 넣는 NgModule 방식이 자주 보입니다. 동작은 하지만 더 이상 권장되는 패턴이 아닙니다. 이 강좌는 모던 앵귤러의 standalone + provideRouter 방식만 다룹니다.라우트 정의하기 #
Routes는 그저 객체 배열입니다. 각 객체가 “어떤 경로일 때 어떤 컴포넌트를 보여줄지"를 기술합니다.
import { Routes } from '@angular/router';
import { HomeComponent } from './pages/home.component';
import { AboutComponent } from './pages/about.component';
import { NotFoundComponent } from './pages/not-found.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'old-about', redirectTo: 'about', pathMatch: 'full' },
{ path: '**', component: NotFoundComponent },
];자주 쓰는 속성들:
path— URL 패턴. 슬래시(/)는 붙이지 않습니다. 빈 문자열('')은 루트 경로component— 그 경로일 때 렌더링할 컴포넌트redirectTo— 다른 경로로 자동 이동.pathMatch: 'full'을 함께 두는 경우가 많음**— 와일드카드. 위에서부터 매칭을 시도하다가 어디에도 안 맞으면 잡아냅니다. 보통 404 페이지 용도로 마지막에 둡니다
<router-outlet>
#
라우트가 매칭되면 그 컴포넌트는 어디에 그려질까요? 바로 <router-outlet> 위치입니다. 보통 최상위 AppComponent의 템플릿에 둡니다.
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink],
template: `
<header>
<a routerLink="/">홈</a>
<a routerLink="/about">소개</a>
</header>
<main>
<router-outlet />
</main>
`,
})
export class AppComponent {}<router-outlet>이 있는 위치에 현재 URL과 매칭되는 컴포넌트가 끼워넣어집니다. 헤더와 푸터는 그대로 남고, 가운데 콘텐츠만 갈아끼는 식입니다.
RouterOutlet과 RouterLink는 standalone 컴포넌트의 imports 배열에 직접 추가해야 한다는 점을 잊지 마세요.
routerLink
#
페이지 이동 링크는 <a> 태그에 href 대신 routerLink 디렉티브를 씁니다.
<a routerLink="/">홈</a>
<a routerLink="/about">소개</a>
<a [routerLink]="['/users', userId]">내 프로필</a>- 문자열로 절대 경로를 직접 넘기는 게 가장 일반적입니다
[routerLink]="[...]"배열 형태는 동적 세그먼트를 끼워 만들 때 편합니다 (/users/123)- 일반
<a href="/about">을 쓰면 브라우저가 페이지를 새로 로드해서 SPA의 장점을 잃습니다. 반드시routerLink를 쓰세요
활성 링크 표시 #
네비게이션 바에서 현재 페이지 링크를 강조하려면 routerLinkActive 디렉티브를 함께 둡니다.
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
홈
</a>
<a routerLink="/about" routerLinkActive="active">소개</a>현재 URL이 해당 routerLink와 매치되면 자동으로 active 클래스가 붙습니다. CSS에서 .active 스타일만 정의해 두면 끝입니다.
/ 같은 루트 경로는 [routerLinkActiveOptions]="{ exact: true }"를 함께 두는 게 좋습니다. 그렇지 않으면 모든 하위 경로에서도 활성으로 잡혀버립니다 (/는 사실상 모든 URL의 접두사니까요).
동적 파라미터 #
상품 상세나 사용자 프로필처럼 URL의 일부가 동적으로 바뀌는 경로는 콜론(:)으로 표시합니다.
export const routes: Routes = [
{ path: 'users/:id', component: UserDetailComponent },
];/users/123, /users/cheolsu 같은 URL이 모두 이 라우트에 매칭됩니다. 컴포넌트 안에서는 ActivatedRoute를 주입받아 파라미터를 읽습니다.
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user-detail',
standalone: true,
template: `<h1>사용자 ID: {{ userId }}</h1>`,
})
export class UserDetailComponent {
private route = inject(ActivatedRoute);
userId = this.route.snapshot.paramMap.get('id');
}ActivatedRoute에서 파라미터를 꺼내는 방법은 두 가지입니다.
snapshot.paramMap.get('id')— 컴포넌트가 처음 만들어진 시점의 값을 한 번 읽습니다. 페이지가 새로 로드될 때마다 컴포넌트도 새로 만들어지니, 대부분의 상황에서 이걸로 충분합니다paramMapObservable로 구독 — 같은 컴포넌트가 살아 있는 채로 파라미터만 바뀌는 경우(예:/users/1→/users/2)에는 snapshot으로는 새 값을 받지 못합니다. 이때는 Observable을 구독해야 합니다
this.route.paramMap.subscribe(params => {
this.userId = params.get('id');
// 새 사용자 데이터 로드 등
});입문 단계에서는 snapshot만 알아도 충분합니다. 같은 컴포넌트 안에서 파라미터를 자주 갈아끼는 화면을 만들게 될 때 Observable 패턴을 떠올리시면 됩니다.
자식 라우트와 중첩 outlet #
여러 페이지가 같은 레이아웃을 공유할 때, 혹은 큰 페이지 안에 sub-section이 있을 때는 **자식 라우트(children)**가 깔끔합니다.
export const routes: Routes = [
{
path: 'users/:id',
component: UserDetailComponent,
children: [
{ path: '', redirectTo: 'profile', pathMatch: 'full' },
{ path: 'profile', component: UserProfileComponent },
{ path: 'posts', component: UserPostsComponent },
],
},
];부모 컴포넌트(UserDetailComponent) 템플릿 안에 또 하나의 <router-outlet>을 둡니다.
@Component({
selector: 'app-user-detail',
standalone: true,
imports: [RouterOutlet, RouterLink, RouterLinkActive],
template: `
<h1>사용자 #{{ userId }}</h1>
<nav>
<a routerLink="profile" routerLinkActive="active">프로필</a>
<a routerLink="posts" routerLinkActive="active">글 목록</a>
</nav>
<router-outlet />
`,
})/users/123/profile로 들어가면 부모의 헤더와 탭은 그대로 두고 가운데 outlet에 UserProfileComponent가 들어갑니다. routerLink="profile"처럼 슬래시 없이 쓰면 현재 라우트 기준 상대 경로로 동작해서 편합니다.
Lazy loading #
기본적으로 모든 라우트의 컴포넌트는 초기 번들에 포함됩니다. 앱이 커지면 첫 로딩이 느려집니다. 자주 쓰지 않는 페이지는 lazy loading으로 분리하면 좋습니다.
export const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'admin',
loadComponent: () => import('./pages/admin.component').then(m => m.AdminComponent),
},
];component 대신 loadComponent에 동적 import()를 넘기면, 사용자가 그 경로로 이동할 때 비로소 해당 모듈이 네트워크로 받아집니다. 빌드 시점에 앵귤러 CLI가 자동으로 별도 청크로 분할해줍니다 — 우리는 라우트 정의만 살짝 바꾸면 됩니다.
component:로 평범하게 짜다가, 빌드 결과의 초기 번들 사이즈가 신경 쓰일 때 관리자 페이지,설정 페이지처럼 “가끔 쓰는 큰 화면” 위주로 옮겨가는 게 실용적입니다.마무리 #
이번 글에서는 앵귤러 Router의 핵심을 살펴봤습니다. 정리하면:
- **
provideRouter(routes)**로 standalone 셋업 Routes배열에path+component(+redirectTo,**와일드카드)<router-outlet>위치에 매칭된 컴포넌트가 그려진다- **
routerLink**로 SPA 전환, **routerLinkActive**로 활성 표시 :id동적 파라미터는inject(ActivatedRoute)+snapshot.paramMap으로 읽기- **
children**으로 중첩 라우트, 부모 템플릿에 또 하나의 outlet - **
loadComponent**로 lazy loading
이 정도면 작은 다중 페이지 앱은 거의 다 만들 수 있습니다. 다음 글인 “앵귤러 기초 강좌 #7 HttpClient 기초"에서는 백엔드 API와 통신하는 법을 다루겠습니다. 이제부터가 진짜 앱다운 앱을 만드는 단계입니다.