はじめに
Angularチュートリアル:Tour of Heroesを読み進めていきます。
開発環境
- OS: Windows10
- Nodeバージョン: 18.18.0
- Angular CLI バージョン: 17.3.8
- エディタ: VSCode
5. ナビゲーションの追加
5. ナビゲーションの追加を読んでいきます。
アプリ構成
https://v17.angular.jp/tutorial/tour-of-heroes/toh-pt5 に添付されている図より。
- アプリのトップ画面にダッシュボード用のボタンとヒーロー一覧画面(こらまで作ってきたHeroesComponent)を表示する用のボタンを配置する
- ダッシュボードからヒーロー詳細画面に遷移できる
- ヒーロー一覧画面からヒーロー詳細画面に遷移できる
- ヒーロー詳細画面には「戻る」ボタンを配置し、押下することで、遷移元の画面に戻ることができる
関連コマンド
ng generate module app-routing --flat --module=app
これにより以下のことが起こります。
- src/app フォルダに app-routing.module.ts が生成される
-
--flat
オプションをつけることで、固有のディr区鳥の代わりに、src/appに生成したファイルが配置される。
-
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule
],
declarations: []
})
export class AppRoutingModule { }
- src/app/app.module.tsが、このapp-routing.module.ts をインポートする
-
--module=app
オプションを付けることで、AppModuleのimports配列に、生成したモジュールを登録するようCLIに指示する。
-
...
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [
...
],
imports: [
...,
AppRoutingModule
],
...
})
ルーティング定義を書くのであれば、app.module.tsに書くでもいけそうですが(構造同じですし)、ルーティングに関する記載はapp.module.tsの外に出して、分割できるのであれば分割するのがAngular流なのかなという理解をしました。
今後ルーティング定義はapp-routing.module.ts に書いていくことになります。
ソースコードを読み解いてみる
Routeの設定: RouterModule.forRoot()
例えば以下のように記載すると、「localhost:4200/heroes」というURLが入力されたとき、HeroesCoponentの内容が記載されるという意味になります。
import { RouterModule, Routes } from '@angular/router';
...
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
「URL末尾に何もなければこのURLのパターンにリダイレクトする」「ID等の数字をURLのパスのパターンとして登録する」こともできます。
import { RouterModule, Routes } from '@angular/router';
...
const routes: Routes = [
// URLの末尾に「/heroes」をつけると、HeroesComponentの内容が表示される
{path: "heroes", component: HeroesComponent},
// URLの末尾に「/dashboard」をつけると、DashboardComponentの内容が表示される
{path: "dashboard", component: DashboardComponent},
// URLの末尾に何も入力されていない(空という文字列に完全一致している)場合、dashboardにリダイレクトする
{path: "", redirectTo: "/dashboard", pathMatch: "full"},
// URLの末尾に「/detail/%id%」をつけることで該当のHeroDetailComponentの内容が表示される
{ path: "detail/:id", component: HeroDetailComponent }
]
その設定を適用させます。
これにより、「このパスのパターンの場合はこれを表示する」という対応をアプリに登録することになります。
import { RouterModule, Routes } from '@angular/router';
...
imports: [ RouterModule.forRoot(routes) ],
そしてそのRouterModuleを共有できるようにexportします。
import { RouterModule, Routes } from '@angular/router';
...
exports: [ RouterModule ]
RouterOutlet
コンポーネント内で<router-outlet>
タグを使います。
<h1>{{title}}</h1>
<router-outlet></router-outlet>
<app-messages></app-messages>
<router-outlet>
を使うことで、現在ルーティングされているコンテンツを 動的に切り替えて表示 できます。
つまり、
-
~/heroes
というURLが指定されたら、<router-outlet>
の部分にはそれに対応するHeroesComponentの内容が表示されるし、 -
~/dashboard
というURLが指定されたら、<router-outlet>
の部分にはそれに対応するDashboardComponsntの内容が表示される
ということです。
(<h1>
と<app-messages>
はルーティングとして何が指定されていようが固定で表示される)
RouterLink
...
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
routerLink
属性を使ってパスを指定することで、そのパスのパターンにマッチするコンポーネントにリンクを飛ばすことができます。
たとえば、Heroesという文字列が表示されたリンクを踏むと、
-
routerLink="/heroes"
のパターンをRouterに問い合わせる - app-routing.module.ts を見ると、そのパターンにマッチするコンポーネントはHeroesComponentだと分かる
- HeroesComponentの内容を表示すべきと判断する(表示場所は
<router-outlet>
)
...
const routes: Routes = [
// このパターンにマッチ
{path: "heroes", component: HeroesComponent},
...
]
という流れで画面が表示されます。
なお、リンクを飛ばすといえば<a href="〇〇>"
ですが、この手法だとページ全体に更新がかかります。
routerLink
を使うことで、ページ全体ではなく該当する箇所のみに更新がかかるようになります。
SPA(Single Page Application)の特性と関連したテクニックといえるでしょう。
RouterLink(その2)
URLにID等の数字を入れてページを表示する場合もあります。
>例: localhost:4200/detail/13
その数字は固定ではなく、13のときもあれば15のときもあります。
こうした動的な値を含んだURLへのリンクを飛ばすテクニックについて書いていきます。
ルーティングのパターンの登録
...
const routes: Routes = [
...
// URLの末尾に「/detail/%id%」をつけることで該当のHeroDetailComponentの内容が表示される
{ path: "detail/:id", component: HeroDetailComponent }
]
パスの指定をよく見ると、:id
と書いています。
コロンを付けることで、 「idは固定の文字列ではなく変数です」という意味を持たせる ことができます。
リンクを飛ばす(RouterLinkの利用)
<a *ngFor="let hero of heroes"
routerLink="/detail/{{hero.id}}">
{{hero.name}}
</a>
Heroオブジェクトのidを取得し、それをダブルカーリーグレイシスを使うことで、変数idに値を動的に埋め込んでいます。
これにより、「動的な値を含むURLへのリンクを飛ばす」を実現します。
補足
ここまでは「idを渡す側」の視点でのテクニックでしたが、「idを受け取った側」のテクニックも記載します。
具体的には、
- idを受け取り、
- HEROES(Heroオブジェクトの配列のモックデータ)から該当のidにマッチするHeroオブジェクトを取得して返す
ということを行います。
@angular/router
のActivatedRoute
を使います。
import { ActivatedRoute } from '@angular/router';
...
export class HeroDetailComponent {
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id =
Number(this.route.snapshot.paramMap.get('id'));
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
}
Number(this.route.snapshot.paramMap.get('id'))
では、URLのパラメタからid
の値を取り出し(~/detail/13 というURLが指定されたのなら"13" という値が取得される)、それをnumberに変換しているという処理をしています。
(AIにも聞いてみた)
このコードは、Angular のルーティング機能を使用して、URL パラメータから id を取得し、それを数値に変換する処理を行っています。以下にステップバイステップで説明します:
1.this.route.snapshot:
- ActivatedRoute の snapshot プロパティを使用して、現在のルートの静的なスナップショットを取得します。snapshot は、ルートが変わるたびに更新されるオブジェクトです。
2.paramMap:
- snapshot.paramMap は、現在のルートに関連するすべてのパラメータを含む ParamMap オブジェクトを返します。ParamMap は、ルートパラメータをキーと値のペアとして管理します。
3.get('id'):
- paramMap.get('id') は、ParamMap から id パラメータの値を取得します。この値は文字列として返されます。
4.Number(...):
- Number(this.route.snapshot.paramMap.get('id')) は、取得した id パラメータの値を数値に変換します。これにより、id が数値として扱われるようになります。
まとめ
このコードは、URL パラメータから id を取得し、それを数値に変換して id 変数に格納する処理を行っています。これにより、例えば /heroes/1 のような URL から 1 という数値を取得することができます。
遷移元の画面に戻る
@angular/common
のLocation
を使います。
ボタン配置
...
<button type="button" (click)="goBack()">Go Back</button>
「Go Back」と表示されているボタンを押下すると、goBack() メソッドが実行されます。
イベントバインディングの仕組みですね。
戻る処理の実装
import { Location } from '@angular/common';
...
export class HeroDetailComponent {
...
constructor(
...,
private location: Location
) {}
...
goBack(): void {
this.location.back();
}
}
@angular/common
のLocation
が提供しているback()
メソッドを実行することで、遷移元の画面に戻ることができます。
つまり、
- 「ダッシュボードからヒーロー詳細画面に遷移したのであれば、戻るボタンを押下すると、ダッシュボード画面が表示される」し、
- 「ヒーロー一覧画面からヒーロー詳細画面に遷移したのであれば、戻るボタンを押下すると、ヒーロー一覧画面が表示される」
ということです。