機能モジュール毎のルーティング設定例
ここでは、ナビゲーションの時にコンポーネント間でパラメータを渡す方法を紹介します。
さらに、アプリケーションを機能モジュール (feature modules) に分割する方法も紹介します。
機能モジュールへの分割
通常、アプリケーションは何らかの機能的な単位に分割できます。開発効率やメンテナンス性を高めるために、 機能毎に独立したモジュールに分割し、互いになるべく依存しないように開発し、最終的に統合する、といったことが良く行われます。
Angular でもモジュール化をサポートする仕組みがあります。
「モジュール」でも触れましたが、Angular アプリケーションには必ずひとつのルート・モジュールがあります。
その他に機能毎の機能モジュール (Feature Modules)に分割可能です。 機能モジュール毎にフォルダを分けるとファイルを整理しやすくなります。
次の例をみてください。
ソースコードはルートモジュールに直接属するコンポーネントの他、func1 と func2 というフォルダの下に機能モジュールが分割されています。
func1 フォルダには、コンポーネントの他、モジュールを定義する func1.module.ts と ルーティングを定義する func1-routing.module.ts があります。func2 フォルダ以下も同様の構成です。
それぞれのフォルダで機能モジュールにまとめ上げた後、ルートモジュールにそれぞれを取り込むことで、アプリケーション全体が統合されています。
モジュール化をすることで、コンポーネント類を整理するだけでなく、ルーティングの設定等個別に行うことができます。
それぞれの機能モジュールからみていきましょう。
具体例: func1 モジュール
func1 を構成するコンポーネント、モジュール、ルーティングモジュールは以下の通りです。
ルーティング・モジュール
まず、func1 モジュール内でのルーティングを構成します。
Func1RoutingModule (func1-routing.module.ts)
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {Func1Component} from './func1.component';
import {Func1DetailComponent} from './func1-detail.component';
const routes: Routes = [
{ path: 'func1', component: Func1Component },
{ path: 'details/:code', component: Func1DetailComponent }
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [
RouterModule
]
})
export class Func1RoutingModule { }
func1 で Func1Component がロードされ、details/:code で Func1DetailComponent がロードされます。コロンに続く文字はパラメータの名前になります。
コンポーネント
Func1Component (src/app/func1/func1.component.tc)
import {Component, OnInit} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {USStateService, USState} from '../services/usstate.service';
@Component({
template: `
<h1>US States</h1>
<ul>
<li *ngFor="let state of states">
<a [routerLink]="['details', state.StateCode]">{{state.StateCode}}</a>
</li>
</ul>
<div *ngIf="!states">Loading...</div>`
})
export class Func1Component implements OnInit {
states: USState[];
constructor(private service: USStateService){}
ngOnInit(){
this.service.getAll()
.subscribe( states => { this.states = states; });
}
}
ここでは「HTTP を用いて非同期で情報を取得する例 (2)」と良く似たスクリプトで、アメリカの州コードをリンクにして列挙しています。
テンプレートでは *ngFor で states プロパティにセットされたデータでループしています。ループ時に [routerLink] でパラメータをセットしています。
出来上がったリンクをみると次のようになります。(CA の場合)
ここで州名はパラメータとして、次の Func1DetailComponent に渡されます。
CA をクリックすると、次のようになります。
少しの間、Loading... という文字が表示され・・・
情報が表示されます。
これを行うコンポーネント Func1DetailComponent は次の通りです。
Func1DetailComponent (src/app/func1-detail.component.ts)
import {Component, OnInit} from '@angular/core';
import {Router, ActivatedRoute, Params } from '@angular/router';
import {USStateService, USState} from '../services/usstate.service';
@Component({
template: `
<h1>US State</h1>
<div *ngIf="usState">
<h2>{{usState.StateName}}</h2>
<div>Name: {{usState.StateName}}</div>
<div>Code: {{usState.StateCode}}</div>
<div>Capital City: {{usState.CapitalCity}}</div>
</div>
<div *ngIf="!usState">Loading...</div>`
})
export class Func1DetailComponent implements OnInit {
usState: USState;
stateName: string;
err: string;
constructor(
private route: ActivatedRoute,
private service: USStateService
){}
ngOnInit(){
this.route.params
.subscribe((params: Params)=>{
this.service.searchState(params["code"])
.subscribe(state => {
this.usState = state[0];
}
,
err => {
this.err = err.toString();
});
});
}
}
渡されたパラメータは ActivatedRoute オブジェクトの params プロパティから取得できます。 DI で ActivatedRoute を取り込めば利用できます。
ngOnInit でパラメータを読み込んでいますが、params は Params のオブザーバブルなので、 subscribe で読み込んでいます。
また、今回は受け取ったパラメータを使って、さらに非同期の HTTP 呼び出しを行い、その結果取得できた値を usState プロパティにセットしています。
ちなみに、usState プロパティは非同期で(通常少し遅れて)取得されるので、 テンプレートでは usState が有効でない場合のパスも必要です。*ngIf で false の処理を入れておきましょう。
機能モジュール
上記のルーティング・モジュールとコンポーネントを、次のようにしてモジュールにまとめ上げます。
Func1Module (func1.module.ts)
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Func1Component } from './func1.component';
import { Func1DetailComponent } from './func1-detail.component';
import { Func1RoutingModule } from './func1-routing.module';
import { USStateService } from '../services/usstate.service';
@NgModule({
imports: [
CommonModule,
FormsModule,
Func1RoutingModule
],
declarations: [
Func1Component,
Func1DetailComponent,
],
providers: [ USStateService ]
})
export class Func1Module {}
ルートモジュールにはこの単位で取りこめるようになるので、個々のモジュールの独立性が高まり管理が非常に容易になります。
具体例: func2 モジュール
func2 を構成するコンポーネント、モジュール、ルーティングモジュールは以下の通りです。
コンポーネント
Func2Component (func2.component.ts)
import {Component} from '@angular/core';
import {Router} from '@angular/router';
@Component({
template: `
<h1>Func 2</h1>
<button (click)="onClick()">Go to Func1</button>`
})
export class Func2Component {
title = 'Func2';
constructor(private router: Router){}
onClick(){
this.router.navigate(['func1']);
}
}
これは単にボタンが一個あって、そのボタンを押すと Func1 にナビゲートされる、というだけです。
これを行うには Router を DI で取り込み、Router オブジェクトの navigate メソッドを利用します。
ルーティング・モジュール
ルーティングモジュールではパス func2 を Func2Component に割り当てているだけです。
Func2RoutingModule (func2-routing.module.ts)
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {Func2Component} from './func2.component';
const routes: Routes = [
{ path: 'func2', component: Func2Component },
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [
RouterModule
]
})
export class Func2RoutingModule { }
機能モジュール
Func2Module (func2.module.ts) は次の通り。
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Func2Component } from './func2.component';
import { Func2RoutingModule } from './func2-routing.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
Func2RoutingModule
],
declarations: [
Func2Component,
],
providers: []
})
export class Func2Module {}
以上で機能モジュール (Feature Moules) のルーティング構成、各種ナビゲートについて説明しました。
ルートモジュール
ルーティング・モジュール
機能モジュールへのルーティングを含む、ルートモジュールのルーティングモジュールは次のように書きます。
AppRoutingModule (app-routing.module.ts)
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { PageNotFoundComponent } from './not-found.component';
const appRoutes: Routes = [
{
path: 'func1',
loadChildren: 'app/func1/func1.module#Func1Module'
},
{
path: 'func2',
loadChildren: 'app/func2/func2.module#Func2Module'
},
//{ path: '', redirectTo: '/func1', pathMatch: 'full' },
{ path: '', component: HomeComponent },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(appRoutes)
],
exports: [
RouterModule
],
providers: []
})
export class AppRoutingModule { }
loadChildren を用いて、パスに応じて取り込む子モジュールを指定しています。
また、ここでは空のパスでホーム用コンポーネント (下記の HomeComponent) を、 ワイルドカード ** で、でたらめなパスを要求されたときの PageNotFoundComponent をそれぞれ指定しています。
ルート・モジュール
上で作成した機能モジュールをルートモジュールに取り込みます。
AppModule (app.module.ts)
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
// Custom Components
import { AppRoutingModule} from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { PageNotFoundComponent } from './not-found.component';
// Feature Modules
import { Func1Module } from './func1/func1.module';
import { Func2Module } from './func2/func2.module';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
PageNotFoundComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
Func1Module,
Func2Module,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
コンポーネント
ここのコンポーネントは単に文字を表示するだけです。
HomeComponent (home.component.ts)
import {Component} from '@angular/core';
@Component({
template: `<h1>{{title}}</h1>`
})
export class HomeComponent {
title = 'Home';
}
PageNotFoundComponent (not-found.component.ts)
import {Component} from '@angular/core';
@Component({
template: `<h1>404 Not Found</h1>`
})
export class PageNotFoundComponent {
}
以上で、モジュールの分割方法と、各種ルーティング設定、ナビゲーション方法について説明しました。