はじめに
Angular技術要素解説シリーズ、第5弾です。
今回は、SPA(シングルページアプリケーション)において「URLに応じて画面を切り替える」ための仕組み、ルーティングを解説します。
Angularは@angular/routerとして、ルーティング機能を標準搭載しています。
この記事で扱う技術要素
- Router(ルーター)の基本
- ルートパラメータ
- Route Guard(ルートガード)
- Resolver(リゾルバー)
- 遅延読み込み(Lazy Loading)
SPAにおけるルーティングとは
従来のWebサイトは、リンクをクリックするたびにサーバーへ新しいHTMLを要求し、ページ全体を再読み込みしていました。
一方AngularのようなSアプリでは、最初に1つのHTMLだけを読み込み、その後はURLの変化に応じてJavaScript側で表示するコンポーネントだけを切り替えるという仕組みを取ります。
この「URLとコンポーネントの対応関係を管理する」機能がルーティングです。
Routerの基本設定
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
import { UserDetailComponent } from './user-detail.component';
export const routes: Routes = [
{ path: '', component: HomeComponent }, // ルートURL(例: /)
{ path: 'about', component: AboutComponent }, // /about
{ path: 'users/:id', component: UserDetailComponent }, // /users/123 のような動的なパス
{ path: '**', redirectTo: '' }, // どれにも一致しない場合のフォールバック
];
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app.component';
import { routes } from './app.routes';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)], // ルート設定をアプリに登録する
});
// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink],
template: `
<nav>
<a routerLink="/">ホーム</a>
<a routerLink="/about">概要</a>
</nav>
<!-- 現在のURLに対応するコンポーネントが、ここに描画される -->
<router-outlet></router-outlet>
`,
})
export class AppComponent {}
<router-outlet>は「現在のURLに対応するコンポーネントの表示場所」を示す特殊なタグです。
routerLinkディレクティブを使うと、通常の<a href="...">のようにページ全体をリロードせず、Angular側でURLとコンポーネントだけを切り替えられます。
ルートパラメータの取得
users/:idのような動的なパスに含まれる値(パラメータ)は、以下のように取得します。
// user-detail.component.ts
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user-detail',
standalone: true,
template: `<p>ユーザーID: {{ userId }}</p>`,
})
export class UserDetailComponent {
private route = inject(ActivatedRoute);
// "users/123" というURLでアクセスされた場合、userIdには "123" が入る
userId = this.route.snapshot.paramMap.get('id');
}
/users/123にアクセスすると、UserDetailComponentが表示され、userIdには"123"という文字列が格納されます。
同じコンポーネント内でURLが/users/123から/users/456に変わるようなケース(パラメータだけが変わる遷移)を検知したい場合は、snapshotではなくroute.paramMapをSignal/Observableとして購読する方法も用意されています。
Route Guard(ルートガード)
ルートガードは、特定のルートへの遷移を許可するかどうかを判定する仕組みです。
「ログインしていないユーザーを、マイページへアクセスさせない」といった認可制御によく使われます。
// auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isLoggedIn()) {
return true; // 遷移を許可
}
router.navigate(['/login']); // ログイン画面へリダイレクト
return false; // 遷移を拒否
};
// app.routes.ts
import { authGuard } from './auth.guard';
export const routes: Routes = [
{ path: 'mypage', component: MyPageComponent, canActivate: [authGuard] },
];
canActivateにガード関数を指定することで、/mypageへのアクセス時に必ずこのチェックが実行されます。
近年のAngularでは、クラスベースのガードではなく、上記のような関数ベースのガード(CanActivateFn)が推奨されています。
Resolver(リゾルバー)
リゾルバーは、ルート遷移が完了する前に、必要なデータを事前に取得しておく仕組みです。
「画面が表示された直後にローディング状態を経てデータが出る」のではなく、「データが揃ってから画面を表示する」という体験を作りたい場合に使います。
// user.resolver.ts
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';
export const userResolver: ResolveFn<User> = (route) => {
const userService = inject(UserService);
const id = route.paramMap.get('id')!;
return userService.fetchUserById(id); // Observable/Promiseを返すと、解決を待ってから画面遷移する
};
// app.routes.ts
export const routes: Routes = [
{
path: 'users/:id',
component: UserDetailComponent,
resolve: { user: userResolver }, // "user"というキーで解決済みデータを渡す
},
];
// user-detail.component.ts
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user-detail',
standalone: true,
template: `<p>{{ user.name }}さんの詳細ページ</p>`,
})
export class UserDetailComponent {
private route = inject(ActivatedRoute);
user = this.route.snapshot.data['user']; // resolveで取得済みのデータをそのまま受け取れる
}
遅延読み込み(Lazy Loading)
大規模なアプリでは、すべてのコンポーネントのコードを最初に一括で読み込むと、初期表示が遅くなってしまいます。
遅延読み込みを使うと、特定のルートに実際にアクセスされたタイミングで、そのルートに必要なコードだけを読み込むようにできます。
// app.routes.ts
export const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'admin',
// "admin"にアクセスされて初めて、admin.routesのファイルが読み込まれる
loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes),
},
{
path: 'settings',
// 単一のコンポーネントを遅延読み込みする場合は loadComponent を使う
loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent),
},
];
loadChildren・loadComponentに渡したimport()は、通常のJavaScriptの動的インポート構文です。
Angular CLIのビルドツール(esbuild)がこれを検知し、admin関連のコードを別ファイル(チャンク)として分割してビルドします。
管理画面のような「一部のユーザーしか使わない機能」を遅延読み込みにすることで、一般ユーザーの初期表示速度を大きく改善できます。
使用場面のまとめ
| 技術要素 | 使用場面 |
|---|---|
| 基本のルート設定 | どのURLでどのコンポーネントを表示するかの対応付け |
| ルートパラメータ | 商品詳細ページ・ユーザープロフィールなど、IDに応じて内容が変わる画面 |
| Route Guard | ログイン必須ページの保護、権限に応じたアクセス制御 |
| Resolver | 画面表示前にデータを揃えておきたい場合(ローディング表示を減らしたい場合) |
| 遅延読み込み | 管理画面など、一部のユーザーしか使わない機能の初期表示速度改善 |
まとめ
| 用語 | 役割 |
|---|---|
| Router | URLとコンポーネントの対応関係を管理する、Angular標準機能 |
<router-outlet> |
現在のルートに対応するコンポーネントが描画される場所 |
| Route Guard | 特定のルートへの遷移を許可するか判定する仕組み |
| Resolver | ルート遷移前に必要なデータを事前取得する仕組み |
| Lazy Loading | 特定ルートに必要なコードを、実際にアクセスされるまで読み込まない仕組み |
次回は、サーバーとの通信を担うHttpClientとRxJSについて解説します。
学習に使った書籍
本記事の内容整理にあたっても、以下の書籍を参考にしました。
- Angular 超入門 第2版 [ 掌田津耶乃 ]
- Angularアプリケーションプログラミング 開発の勘所と手順をしっかりマスター [ 山田祥寛 ]
- Angular Webアプリ開発スタートブック [ 大澤文孝 ]
※上記リンクはアフィリエイトリンクを含みます。