機能モジュール毎のルーティング設定例

ここでは、ナビゲーションの時にコンポーネント間でパラメータを渡す方法を紹介します。

さらに、アプリケーションを機能モジュール (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 {
}

以上で、モジュールの分割方法と、各種ルーティング設定、ナビゲーション方法について説明しました。