HTML5テクニカルノート
Angular 2入門 05: Routerを使う
- ID: FN1701001
- Technique: HTML5 / JavaScript
- Package: Angular 2.1
「Angular 2入門 04: サービスをつくる」(以下「Angular 2入門 04」)の作例(コード002)のコードをさらにコンポーネントに分け、Routerでコンポーネントを切り替えます。Routerはページの表示やURLのパスを変えるナビゲーションの仕組みです。
01 コンポーネントのモジュールからリストのコンポーネントを分ける
前出「Angular 2入門 04」コード002では、コンポーネントのモジュール(app.component)が項目をリスト表示しました。このリスト表示の仕事を、別のモジュール(heroines.component)に分けます(「app」フォルダ内)。メインのコンポーネントとして、ナビゲーションに専念させるためです。リスト表示のコンポーネントは、つぎのように親コンポーネントのコードのコードのほとんどを受け継ぎます。
app.component.ts→heroines.component.tsimport {Component, OnInit} from '@angular/core'; @Component({ selector: 'my-heroines', // 'my-app', /* <h1>{{title}}</h1> */ template: ` <h2>ヒロインたち</h2> <ul class="heroines"> </ul> <my-heroine-detail [heroine]="selectedHeroine"></my-heroine-detail> `, /* , providers: [HeroineService] */ }) // export class AppComponent { export class HeroinesComponent implements OnInit { // title = 'ヒロイン一覧'; }
すると、親コンポーネント(app.component)は、一旦つぎのような質素なつくりになります。子コンポーネントのナビゲーションは、このあと加えていくつもりです。
app.component.ts// import {Component, OnInit} from '@angular/core'; import {Component} from '@angular/core'; // import {Heroine} from './heroine'; // import {HeroineService} from './heroine.service'; @Component({ selector: 'my-app', template: ` <h1>{{title}}</h1> <my-heroines></my-heroines> ` /* styles: [` `], providers: [HeroineService] */ }) export class AppComponent { title = 'ヒロイン一覧'; /* heroines: Heroine[]; selectedHeroine: Heroine; constructor(private heroineService: HeroineService) { } onSelect(heroine: Heroine): void { } getHeroines(): void { } ngOnInit(): void { } */ }
また、アプリケーションのモジュール(app.module)に、つぎのようにリスト表示の子コンポーネント(heroines.component)を加えなければなりません。併せて、サービスのモジュール(heroine.service)も組み込みます。そして、デコレータ関数NgModule
に渡す引数のオブジェクトにproviders
として定めました。モジュールの組み立てを変えただけですから、アプリケーションの動きは変わりません(図001)。ここまで書いたリスト表示(heroines.component)とメインのコンポーネント(app.component)およびアプリケーションのモジュール(app.module)を、以下のコード001にまとめました。
app.module.tsimport {HeroinesComponent} from './heroines.component'; import {HeroineService} from './heroine.service'; @NgModule({ declarations: [ HeroineDetailComponent, HeroinesComponent ], providers: [HeroineService], }) export class AppModule {}
図001■リストの項目をクリックすると下にその情報が表れる
コード001■リスト表示とメインのコンポーネントおよびアプリケーションのモジュール
heroines.component.ts
import {Component, OnInit} from '@angular/core';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
selector: 'my-heroines',
template: `
<h2>ヒロインたち</h2>
<ul class="heroines">
<li *ngFor="let heroine of heroines"
[class.selected]="heroine === selectedHeroine"
(click)="onSelect(heroine)">
<span class="badge">{{heroine.id}}</span> {{heroine.name}}
</li>
</ul>
<my-heroine-detail [heroine]="selectedHeroine"></my-heroine-detail>
`,
styles: [`
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroines {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroines li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: 0.5em;
padding: 0.3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroines li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroines li:hover {
color: #607D8B;
background-color: #DDD;
left: 0.1em;
}
.heroines .text {
position: relative;
top: -3px;
}
.heroines .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
`]
})
export class HeroinesComponent implements OnInit {
heroines: Heroine[];
selectedHeroine: Heroine;
constructor(private heroineService: HeroineService) { }
onSelect(heroine: Heroine): void {
this.selectedHeroine = heroine;
}
getHeroines(): void {
this.heroineService.getHeroines().then((heroines: Heroine[]) => this.heroines = heroines);
}
ngOnInit(): void {
this.getHeroines();
}
}
import {Component} from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<my-heroines></my-heroines>
`
})
export class AppComponent {
title = 'ヒロイン一覧';
}
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {AppComponent} from './app.component';
import {HeroineDetailComponent} from './heroine-detail.component';
import {HeroinesComponent} from './heroines.component';
import {HeroineService} from './heroine.service';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HeroineDetailComponent,
HeroinesComponent
],
providers: [HeroineService],
bootstrap: [AppComponent]
})
export class AppModule {}
02 Routerを加える
メインのコンポーネント(app.component)は、子コンポーネントの表示をナビゲートして切り替えることが役割になりました。そのためには、Routerを加えて設定しなければなりません。ナビゲーションには、ブラウザのアドレスバーに示すURLも含みます。そのため、HTMLドキュメント(index.html)には、つぎのようにbase
要素を定めます。
index.html<head> <base href="/"> </head>
アプリケーションのモジュール(app.module)には、RouterModule
クラスをimport
します。すると、NgModule
デコレータ関数に渡す引数のオブジェクトのimports
プロパティに、RouterModule.forRoot()
メソッドでつぎのようにURLのパス(path
プロパティ)と表示するコンポーネント(component
ブロパティ)が定められます。
app.module.tsimport {RouterModule} from '@angular/router'; @NgModule({ imports: [ FormsModule, RouterModule.forRoot([ { path: 'heroines', component: HeroinesComponent } ]) ], }) export class AppModule {}
するとつぎに、上記で定めたパスにいつ切り替え、コンポーネントをどこに表示するかです。メインのコンポーネント(app.component)のコードは、つぎのように書き替えます。<a>
要素のリンクで切り替えるため、RouterLink
ディレクティブにパスを与えます。そして、RouterOutlet
ディレクティブを加えれば、リンクしたパスのコンポーネントがそこに差し込まれます。
app.component.ts@Component({ template: ` <h1>{{title}}</h1> <a routerLink="/heroines">ヒロインたち</a> <router-outlet></router-outlet> ` /* <my-heroines></my-heroines> */ }) export class AppComponent { }
図002■リンクをクリックすると下にリストが表れる
コード002■アプリケーションとメインのコンポーネント
app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {AppComponent} from './app.component';
import {HeroineDetailComponent} from './heroine-detail.component';
import {HeroinesComponent} from './heroines.component';
import {HeroineService} from './heroine.service';
@NgModule({
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([
{
path: 'heroines',
component: HeroinesComponent
}
])
],
declarations: [
AppComponent,
HeroineDetailComponent,
HeroinesComponent
],
providers: [HeroineService],
bootstrap: [AppComponent]
})
export class AppModule {}
import {Component} from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<a routerLink="/heroines">ヒロインたち</a>
<router-outlet></router-outlet>
`
})
export class AppComponent {
title = 'ヒロイン一覧';
}
03 ダッシュボードのコンポーネントを加える
初めにデフォルトとして、項目リストの前にダッシュボードを表示することにします。そのコンポーネント(dashboard.component)をつぎのように定めましょう。テンプレートはとりあえず「ダッシュボード」というテキストを示すだけです。
dashboard.component.tsimport {Component} from '@angular/core'; @Component({ selector: 'my-dashboard', template: '<h3>ダッシュボード</h3>' }) export class DashboardComponent {}
アプリケーションのモジュール(app.module)に、つぎのようにデコレータ関数(@NgModule
)の引数でコンポーネント(DashboardComponent)を加えます。RouterModule.forRoot()
メソッドには、パス(dashboard)を定めたほか、指定がない('')ときのデフォルト転送先(redirectTo
)としました(「Redirecting routes」参照)。そして、declarations
の配列に納めています。
app.module.tsimport {DashboardComponent} from './dashboard.component'; @NgModule({ imports: [ RouterModule.forRoot([ { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent }, ]) ], declarations: [ AppComponent, DashboardComponent, ], }) export class AppModule {}
メインのコンポーネントには、つぎのように<a>要素でダッシュボードへのリンクを加えます。パスはrouterLink
ディレクティブで与えます。これで、初めにダッシュボードが表示されるようになりました(図003)。
app.component.ts@Component({ template: ` <nav> <a routerLink="/dashboard">ダッシュボード</a> <a routerLink="/heroines">ヒロインたち</a> </nav> ` }) export class AppComponent { }
図003■初めにダッシュボードが表示される
04 ダッシュボードに上位のリストを示す
ダッシュボードには、リストの上位4項目を掲げましょう。コンポーネントのクラス(DashboardComponent)に以下のようにOnInit
クラスを実装し、コンストラクタでサービス(HeroineService)のインスタンスをプロパティ(heroineService)に与えます。そして、ngOnInit()
メソッドが呼び出されたとき、リスト項目から取り出した上位4つのエレメントを配列でプロパティ(heroines)に定めました。
テンプレートは外部ファイル(dashboard.component.html)とし、デコレータ関数(@Component
)にプロパティtemplateUrl
で定めました。このとき、moduleId
にmodule.id
を与えなければなりません。
dashboard.component.ts// import {Component} from '@angular/core'; import {Component, OnInit} from '@angular/core'; import {Heroine} from './heroine'; import {HeroineService} from './heroine.service'; @Component({ moduleId: module.id, selector: 'my-dashboard', // template: '<h3>ダッシュボード</h3>' templateUrl: 'dashboard.component.html' }) // export class DashboardComponent {} export class DashboardComponent implements OnInit { heroines: Heroine[] = []; constructor(private heroineService: HeroineService) {} ngOnInit(): void { this.heroineService.getHeroines() .then((heroines: Heroine[]) => this.heroines = heroines.slice(0, 4)); } }
テンプレートはつぎのようにngFor
ディレクティブで、プロパティ(heroines)の配列から取り出した項目を順に要素に加えています(図004)。ダッシュボードを加えるために書き替えた3つのモジュールは、以下のコード003にまとめたとおりです。
dashboard.component.html<h3>トップヒロイン</h3> <div class="grid grid-pad"> <div *ngFor="let heroine of heroines" class="col-1-4"> <div class="module heroine"> <h4>{{heroine.name}}</h4> </div> </div> </div>
図004■ダッシュボードに示された上位のリスト
コード003■ダッシュボードを加えるために書き替えた3つのモジュール
dashboard.component.ts
import {Component, OnInit} from '@angular/core';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
moduleId: module.id,
selector: 'my-dashboard',
templateUrl: 'dashboard.component.html'
})
export class DashboardComponent implements OnInit {
heroines: Heroine[];
constructor(private heroineService: HeroineService) {}
ngOnInit(): void {
this.heroineService.getHeroines()
.then((heroines: Heroine[]) => this.heroines = heroines.slice(0, 4));
}
}
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {AppComponent} from './app.component';
import {HeroineDetailComponent} from './heroine-detail.component';
import {HeroinesComponent} from './heroines.component';
import {HeroineService} from './heroine.service';
import {DashboardComponent} from './dashboard.component';
@NgModule({
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
component: DashboardComponent
},
{
path: 'heroines',
component: HeroinesComponent
}
])
],
declarations: [
AppComponent,
DashboardComponent,
HeroineDetailComponent,
HeroinesComponent
],
providers: [HeroineService],
bootstrap: [AppComponent]
})
export class AppModule {}
import {Component} from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">ダッシュボード</a>
<a routerLink="/heroines">ヒロインたち</a>
</nav>
<router-outlet></router-outlet>
`
})
export class AppComponent {
title = 'ヒロイン一覧';
}
05 詳細情報のコンポーネントからテンプレートを外部ファイルに分ける
詳細情報のコンポーネントも、テンプレートを外部ファイル(heroine-detail.component.html)に分けましょう。手順は前項と同じです。つぎのように、デコレータ関数(@Component
)に渡すオブジェクトに、プロパティtemplateUrl
とmoduleId
を定めます。もちろん、アプリケーションの動きは、そのまま変わりません。
heroine-detail.component.tsheroine-detail.component.html@Component({ moduleId: module.id, /* template: ` <div *ngIf="heroine"> <h2>{{heroine.name}}の情報</h2> <div><label>番号: </label>{{heroine.id}}</div> <div> <label>名前: </label> <input [(ngModel)]="heroine.name" placeholder="名前"> </div> </div> ` */ templateUrl: 'heroine-detail.component.html' }) export class HeroineDetailComponent { }
<div *ngIf="heroine"> <h2>{{heroine.name}}の情報</h2> <div><label>番号: </label>{{heroine.id}}</div> <div> <label>名前: </label> <input [(ngModel)]="heroine.name" placeholder="名前"> </div> </div>
06 詳細情報からボタンでダッシュボードに戻る
詳細情報のテンプレートには、つぎのようにダッシュボードに<button>要素を加えます。click
イベントで呼び出すメソッド(goBack())は、この後コンポーネント(heroine-detail.component)に定めます。
heroine-detail.component.html<div *ngIf="heroine"> <div> </div> <button (click)="goBack()">戻る</button> </div>
詳細情報のコンポーネント(heroine-detail.component)には、つぎのように@angularのモジュールの3つのクラス(ActivatedRoute
とParams
およびLocation
)、そしてサービスのクラス(HeroineService)をimport
します。すると、コンストラクタには3つのクラスのインスタンスが渡されますので、それらをプロパティに納めておきます。新たに加えたメソッド(goBack())は、Location.back()
メソッドでブラウザの履歴にしたがってURLを戻ります(図005)。
heroine-detail.component.tsimport {ActivatedRoute, Params} from '@angular/router'; import {Location} from '@angular/common'; import {HeroineService} from './heroine.service'; export class HeroineDetailComponent { constructor( private heroineService: HeroineService, private route: ActivatedRoute, private location: Location ) {} goBack(): void { this.location.back(); } }
図005■詳細情報のボタンをクリックするとダッシュボードに戻る
07 ダッシュボードから詳細情報のURLに移る
ダッシュボードの上位リストをリンクにして、クリックしたら詳細情報のID番号を加えた゜URLに移るようにしてみます。テンプレートは、つぎのように項目を<a>
要素に書き替えます。リンクを動的に決めるときは、routerLink
ディレクティブを角かっこ[]
の中に書き、値となるパスの要素は配列に加えます。アプリケーションのモジュール(app.module)に、RouterModule.forRoot()
メソッドでそのパスを加えれば、クリックした項目の詳細情報にURLが移ります。ただし、選択した項目のデータはコンポーネントに送られませんので、情報が何も示されません(図006)。
dashboard.component.htmlapp.module.ts<h3>トップヒロイン</h3> <div class="grid grid-pad"> <!--<div *ngFor="let heroine of heroines" class="col-1-4">--> <a *ngFor="let heroine of heroines" [routerLink]="['/detail', heroine.id]" class="col-1-4"> </a> </div>
@NgModule({ imports: [ RouterModule.forRoot([ { path: 'detail/:id', component: HeroineDetailComponent }, ]) ], }) export class AppModule {}
図006■ダッシュボードの上位リストをクリックすると詳細情報のURLに移る
→
08 ダッシュボードで選択した項目のデータを詳細情報のコンポーネントに示す
詳細情報のURLには、id番号が添えてありました。この値は、後に述べるやり方で調べられます。また、サービス(heroine.service)のモジュールからは、idの含まれたすべての項目のリストが得られました。すると、idを頼りにして項目の情報が取り出せるということです。そのメソッド(getHeroine())も、つぎのようにサービスに加えましょう。引数の値(id)から、Array.find()
メソッドで当てはまる項目のオブジェクトを探して返します。
heroine.service.tsexport class HeroineService { getHeroines(): Promise<Heroine[]> { } getHeroine(id: number): Promise<Heroine> { return this.getHeroines() .then((heroines: Heroine[]) => heroines.find((heroine: Heroine) => heroine.id === id)); } }
詳細情報のコンポーネント(heroine-detail.component)は、コンストラクタでサービス(HeroineService)とActivatedRoute
オブジェクトをプロパティ(heroineServiceおよびroute)に定めてありました。さらに、RxJSのオペレータswitchMap
とAngular 2のクラスOnInit
を、つぎのようにimport
します。ngOnInit()
メソッドは、ActivatedRoute.params
プロパティにswitchMap
オペレータで、サービスのメソッド(getHeroine())から引数のidのオブジェクトを受け取るよう定めます(引数に+
演算子を加えたのは数値に変換するためです)。オブジェクトが得られたら、subscribe
オペレータでプロパティに定めます(「これだけ知ってれば大丈夫(かもしれない)RxJS5の厳選オペレータ」参照)。
heroine-detail.component.tsimport 'rxjs/add/operator/switchMap'; // import {Component, Input} from '@angular/core'; import {Component, Input, OnInit} from '@angular/core'; import {ActivatedRoute, Params} from '@angular/router'; // export class HeroineDetailComponent { export class HeroineDetailComponent implements OnInit { constructor( private heroineService: HeroineService, private route: ActivatedRoute, ) {} ngOnInit(): void { this.route.params .switchMap((params: Params) => this.heroineService.getHeroine(+params['id'])) .subscribe((heroine: Heroine) => this.heroine = heroine); } }
これで詳細情報のコンポーネントに、パスのidに一致する項目、つまりダッシュボードで選択された項目のデータが示されます(図007)。ダッシュボードから詳細情報の画面に移すために、書き改めたモジュールとテンプレートは以下のコード004にまとめました。
図007■ダッシュボードのでクリックした項目のデータが詳細情報に示される
コード004■ダッシュボードから詳細情報の画面に移すために書き改めたモジュールとテンプレート
heroine.service.ts
import {Injectable} from '@angular/core';
import {Heroine} from './heroine';
import {HEROINES} from './mock-heroines';
@Injectable()
export class HeroineService {
getHeroines(): Promise<Heroine[]> {
return Promise.resolve(HEROINES);
}
getHeroine(id: number): Promise<Heroine> {
return this.getHeroines()
.then((heroines: Heroine[]) => heroines.find((heroine: Heroine) => heroine.id === id));
}
}
import 'rxjs/add/operator/switchMap';
import {Component, Input, OnInit} from '@angular/core';
import {ActivatedRoute, Params} from '@angular/router';
import {Location} from '@angular/common';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
moduleId: module.id,
selector: 'my-heroine-detail',
templateUrl: 'heroine-detail.component.html'
})
export class HeroineDetailComponent implements OnInit {
@Input()
heroine: Heroine;
constructor(
private heroineService: HeroineService,
private route: ActivatedRoute,
private location: Location
) {}
ngOnInit(): void {
this.route.params
.switchMap((params: Params) => this.heroineService.getHeroine(+params['id']))
.subscribe((heroine: Heroine) => this.heroine = heroine);
}
goBack(): void {
this.location.back();
}
}
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {AppComponent} from './app.component';
import {HeroineDetailComponent} from './heroine-detail.component';
import {HeroinesComponent} from './heroines.component';
import {HeroineService} from './heroine.service';
import {DashboardComponent} from './dashboard.component';
@NgModule({
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
component: DashboardComponent
},
{
path: 'heroines',
component: HeroinesComponent
},
{
path: 'detail/:id',
component: HeroineDetailComponent
}
])
],
declarations: [
AppComponent,
DashboardComponent,
HeroineDetailComponent,
HeroinesComponent
],
providers: [HeroineService],
bootstrap: [AppComponent]
})
export class AppModule {}
<div *ngIf="heroine">
<h2>{{heroine.name}}の情報</h2>
<div><label>番号: </label>{{heroine.id}}</div>
<div>
<label>名前: </label>
<input [(ngModel)]="heroine.name" placeholder="名前">
</div>
<button (click)="goBack()">戻る</button>
</div>
<h3>トップヒロイン</h3>
<div class="grid grid-pad">
<a *ngFor="let heroine of heroines" [routerLink]="['/detail', heroine.id]" class="col-1-4">
<div class="module heroine">
<h4>{{heroine.name}}</h4>
</div>
</a>
</div>
09 テンプレートとスタイルを外部ファイルに分ける
項目リストのコンポーネント(heroines.component.ts)は、テンプレートとスタイルを別ファイルに分けましょう。前述のとおりテンプレートのHTMLファイル(heroines.component.html)は、デコレータ関数(@Component
)に渡すオブジェクトのtemplateUrl
プロパティに定めます。スタイルのCSSファイル(heroines.component.css)は、つぎのようにstyleUrls
プロパティに配列エレメントとして加えます。moduleId
プロパティにmodule.id
を与えることも忘れないでください。
heroines.component.ts@Component({ moduleId: module.id, selector: 'my-heroines', /* template: ` `, */ templateUrl: 'heroines.component.html', /* styles: [` `] */ styleUrls: ['heroines.component.css'] }) export class HeroinesComponent implements OnInit { }
heroines.component.html<h2>ヒロインたち</h2> <ul class="heroines"> <li *ngFor="let heroine of heroines" [class.selected]="heroine === selectedHeroine" (click)="onSelect(heroine)"> <span class="badge">{{heroine.id}}</span> {{heroine.name}} </li> </ul> <my-heroine-detail [heroine]="selectedHeroine"></my-heroine-detail>
heroines.component.css.selected { background-color: #CFD8DC !important; color: white; } .heroines { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroines li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: 0.5em; padding: 0.3em 0; height: 1.6em; border-radius: 4px; } .heroines li.selected:hover { background-color: #BBD8DC !important; color: white; } .heroines li:hover { color: #607D8B; background-color: #DDD; left: 0.1em; } .heroines .text { position: relative; top: -3px; } .heroines .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; }
10 Routerのモジュールを分ける
アプリケーション(app.module)のモジュール(app.module)から、Routerの仕事も分けることにします。Routerのモジュール(app-routing.module)には、アプリケーションのコードをコピーしてつぎのように書き替えます。パスとコンポーネントの配列は、わかりやすいように予め定数(routes)に納めました。RouterModule
クラスは、デコレータ関数(@NgModule
)に渡すオブジェクトのexports
プロパティに、配列エレメントとして与えます。
app.module.ts→app-routing.module.tsimport {NgModule} from '@angular/core'; /* import {BrowserModule} from '@angular/platform-browser'; import {FormsModule} from '@angular/forms'; import {RouterModule} from '@angular/router'; */ import {RouterModule, Routes} from '@angular/router'; // import {AppComponent} from './app.component'; import {HeroineDetailComponent} from './heroine-detail.component'; import {HeroinesComponent} from './heroines.component'; // import {HeroineService} from './heroine.service'; import {DashboardComponent} from './dashboard.component'; const routes: Routes = [ { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent }, { path: 'heroines', component: HeroinesComponent }, { path: 'detail/:id', component: HeroineDetailComponent } ]; @NgModule({ imports: [ /* BrowserModule, FormsModule, RouterModule.forRoot([ ]) */ RouterModule.forRoot(routes) ], /* declarations: [ AppComponent, DashboardComponent, HeroineDetailComponent, HeroinesComponent ], providers: [HeroineService], bootstrap: [AppComponent] */ exports: [RouterModule] }) // export class AppModule {} export class AppRoutingModule {}
アプリケーション(app.module)は、つぎのようにRouterのコードを除く替わりに、Routerモジュールのクラス(AppRoutingModule)を読み込みます。ここまで書き替えたリスト表示(heroines.component)とRouterおよびアプリケーションのモジュールは、以下のコード005にまとめました。なお、リスト表示のコンポーネントから外部ファイルに分けたテンプレート(heroines.component.html)とスタイル(heroines.component.css)は前掲のとおりですので省きました。
app.module.tsimport {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {FormsModule} from '@angular/forms'; // import {RouterModule} from '@angular/router'; import {AppComponent} from './app.component'; import {HeroineDetailComponent} from './heroine-detail.component'; import {HeroinesComponent} from './heroines.component'; import {HeroineService} from './heroine.service'; import {DashboardComponent} from './dashboard.component'; import {AppRoutingModule} from './app-routing.module'; @NgModule({ imports: [ BrowserModule, FormsModule, /* RouterModule.forRoot([ { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent }, { path: 'heroines', component: HeroinesComponent }, { path: 'detail/:id', component: HeroineDetailComponent } ]) */ AppRoutingModule ], declarations: [ AppComponent, DashboardComponent, HeroineDetailComponent, HeroinesComponent ], providers: [HeroineService], bootstrap: [AppComponent] }) export class AppModule {}
コード005■書き替えたリスト表示とRouterおよびアプリケーションのモジュール
heroines.component.ts
import {Component, OnInit} from '@angular/core';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
moduleId: module.id,
selector: 'my-heroines',
templateUrl: 'heroines.component.html',
styleUrls: ['heroines.component.css']
})
export class HeroinesComponent implements OnInit {
heroines: Heroine[];
selectedHeroine: Heroine;
constructor(private heroineService: HeroineService) {}
onSelect(heroine: Heroine): void {
this.selectedHeroine = heroine;
}
getHeroines(): void {
this.heroineService.getHeroines().then((heroines: Heroine[]) => this.heroines = heroines);
}
ngOnInit(): void {
this.getHeroines();
}
}
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {HeroineDetailComponent} from './heroine-detail.component';
import {HeroinesComponent} from './heroines.component';
import {DashboardComponent} from './dashboard.component';
const routes: Routes = [
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
component: DashboardComponent
},
{
path: 'heroines',
component: HeroinesComponent
},
{
path: 'detail/:id',
component: HeroineDetailComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {AppComponent} from './app.component';
import {HeroineDetailComponent} from './heroine-detail.component';
import {HeroinesComponent} from './heroines.component';
import {HeroineService} from './heroine.service';
import {DashboardComponent} from './dashboard.component';
import {AppRoutingModule} from './app-routing.module';
@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule
],
declarations: [
AppComponent,
DashboardComponent,
HeroineDetailComponent,
HeroinesComponent
],
providers: [HeroineService],
bootstrap: [AppComponent]
})
export class AppModule {}
11 詳細情報のナビゲーションを整理する
リスト表示(heroines.component)のコンポーネントで項目をクリックすると、詳細情報はリストの下に示されました(前掲図005参照)。これを、ダッシュボードと同じように、別画面で表示するようにしましょう。そのため、テンプレート(heroines.component.html)はつぎのように書き替えて、ボタンで詳細情報のURLに移るようにします。ボタンクリックで呼び出すメソッド(gotoDetail())は、この後コンポーネントに加えます。
heroines.component.html<ul class="heroines"> </ul> <div *ngIf="selectedHeroine"> <h2> ヒロイン「{{selectedHeroine.name}}」 </h2> <button (click)="gotoDetail()">詳細を見る</button> </div> <!-- <my-heroine-detail [heroine]="selectedHeroine"></my-heroine-detail> -->
リスト表示(heroines.component)のコンポーネントは、つぎのようにRouter
クラスをimport
し、コンストラクタで引数として受け取ったインスタンスをプロパティ(router)に与えます。すると、Router.navigate()
メソッドで、引数に渡した配列の定めるパスに移れるのです。ボタンクリックで呼び出されるメソッド(gotoDetail())に、この処理を加えました。これで、リストの項目を選んだ後、ボタンのクリックでダッシュボードと同じ詳細情報の画面が示されます。
heroines.component.tsimport {Router} from '@angular/router'; export class HeroinesComponent implements OnInit { constructor( private router: Router, private heroineService: HeroineService ) {} gotoDetail(): void { this.router.navigate(['/detail', this.selectedHeroine.id]); } }
12 メインのコンポーネントにスタイルを定める
メインのコンポーネントにもスタイルを定めましょう。CSSファイルは「Angular Example - Tour of Heroes: Part 5」のapp.component.cssを使わせてもらいます。つぎのように、ファイルまたはコードをそのままコピーしてしまって構いません。
app.component.cssh1 { font-size: 1.2em; color: #999; margin-bottom: 0; } h2 { font-size: 2em; margin-top: 0; padding-top: 0; } nav a { padding: 5px 10px; text-decoration: none; margin-top: 10px; display: inline-block; background-color: #eee; border-radius: 4px; } nav a:visited, a:link { color: #607D8B; } nav a:hover { color: #039be5; background-color: #CFD8DC; } nav a.active { color: #039be5; }
そのうえで、メインのコンポーネント(app.component)のデコレータ関数(@Component
)には、引数のオブジェクトにプロパティmoduleId
とstyleUrls
をつぎのように与えます。もうひとつ、リンクの<a>
要素には、routerLinkActive
ディレクティブにクラス(active)を定めました。リンクがアクティブになると、このクラスは動的に加えられます。ダッシュボードとリスト表示のどちらが選ばれているかで、スタイルが変わるということです(図008)。書き改めたリスト表示のコンポーネント(app.component.ts)のテンプレートとコード、およびメインのコンポーネント(app.component)は、以下のコード006にまとめました。
app.component.ts@Component({ moduleId: module.id, template: ` <nav> <a routerLink="/dashboard" routerLinkActive="active">ダッシュボード</a> <a routerLink="/heroines" routerLinkActive="active">ヒロインたち</a> </nav> `, styleUrls: ['app.component.css'] }) export class AppComponent { }
図008■アクティブなリンクのスタイルが変わる
コード006■リスト表示のコンポーネントのテンプレートとコードおよびメインのコンポーネント
heroines.component.html
<h2>ヒロインたち</h2>
<ul class="heroines">
<li *ngFor="let heroine of heroines"
[class.selected]="heroine === selectedHeroine"
(click)="onSelect(heroine)">
<span class="badge">{{heroine.id}}</span> {{heroine.name}}
</li>
</ul>
<div *ngIf="selectedHeroine">
<h2>
ヒロイン「{{selectedHeroine.name}}」
</h2>
<button (click)="gotoDetail()">詳細を見る</button>
</div>
import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {Heroine} from './heroine';
import {HeroineService} from './heroine.service';
@Component({
moduleId: module.id,
selector: 'my-heroines',
templateUrl: 'heroines.component.html',
styleUrls: ['heroines.component.css']
})
export class HeroinesComponent implements OnInit {
heroines: Heroine[];
selectedHeroine: Heroine;
constructor(
private router: Router,
private heroineService: HeroineService
) {}
onSelect(heroine: Heroine): void {
this.selectedHeroine = heroine;
}
getHeroines(): void {
this.heroineService.getHeroines().then((heroines: Heroine[]) => this.heroines = heroines);
}
ngOnInit(): void {
this.getHeroines();
}
gotoDetail(): void {
this.router.navigate(['/detail', this.selectedHeroine.id]);
}
}
import {Component} from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard" routerLinkActive="active">ダッシュボード</a>
<a routerLink="/heroines" routerLinkActive="active">ヒロインたち</a>
</nav>
<router-outlet></router-outlet>
`,
styleUrls: ['app.component.css']
})
export class AppComponent {
title = 'ヒロイン一覧';
}
13 スタイルを仕上げる
Routerを使ったアプリケーションの動きそのものはこれででき上がりといえます。後は、スタイルを整えましょう。使うのは、やはり前掲「Angular Example - Tour of Heroes: Part 5」のCSSファイルです。詳細情報のコンポーネント(heroine-detail.component)には、同じ名前のつぎのCSSファイル(heroine-detail.component.css)を定めます。デコレータ関数(@Component
)にはmoduleId
がすでに与えられていますから、styleUrls
の配列エレメントに加えるだけです。
heroine-detail.component.csslabel { display: inline-block; width: 3em; margin: .5em 0; color: #607D8B; font-weight: bold; } input { height: 2em; font-size: 1em; padding-left: .4em; } button { margin-top: 20px; font-family: Arial; background-color: #eee; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; cursor: hand; } button:hover { background-color: #cfd8dc; } button:disabled { background-color: #eee; color: #ccc; cursor: auto; }
heroine-detail.component.ts@Component({ moduleId: module.id, templateUrl: 'heroine-detail.component.html', styleUrls: ['heroine-detail.component.css'] }) export class HeroineDetailComponent implements OnInit { }
ダッシュボードのコンポーネント(dashboard.component)にも、「Angular Example - Tour of Heroes: Part 5」から同じ名前のCSSファイルを定めます。つぎのコードをそのままコピーしてしまって構いません。こちらも、styleUrls
に配列エレメントとして加えるだけです。これで、見栄えはずっとよくなります(図009)。
dashboard.component.css[class*='col-'] { float: left; padding-right: 20px; padding-bottom: 20px; } [class*='col-']:last-of-type { padding-right: 0; } a { text-decoration: none; } *, *:after, *:before { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } h3 { text-align: center; margin-bottom: 0; } h4 { position: relative; } .grid { margin: 0; } .col-1-4 { width: 25%; } .module { padding: 20px; text-align: center; color: #eee; max-height: 120px; min-width: 120px; background-color: #607D8B; border-radius: 2px; } .module:hover { background-color: #EEE; cursor: pointer; color: #607d8b; } .grid-pad { padding: 10px 0; } .grid-pad > [class*='col-']:last-of-type { padding-right: 20px; } @media (max-width: 600px) { .module { font-size: 10px; max-height: 75px; } } @media (max-width: 1024px) { .grid { margin: 0; } .module { min-width: 60px; } }
dashboard.component.ts@Component({ moduleId: module.id, templateUrl: 'dashboard.component.html', styleUrls: ['dashboard.component.css'] }) export class DashboardComponent implements OnInit { }
図009■スタイルが与えられたダッシュボード
余裕があれば、ページのHTMLドキュメント(index.html)が読み込んでいるスタイルシート(styles.css)も、「Angular Example - Tour of Heroes: Part 5」からコピーして上書きしてしまうと、スタイルがさらに細かく加えられます。リスト表示の下に表れるボタンのスタイルが整い、ポインタを重ねたとき指差しカーソルに変わります(図010)。ファイルの数も増えましたので、改めてすべてのコードをこのページに掲げることは避けます。Plunkerの「Angular 2 Example - Tour of Heroines: Part 5」でコードと動きを確かめられるようにアップロードしましたので、そちらをご覧ください。
図010■リスト表示の下に表れるボタンにスタイルが加わった
- Angular 2: とにかくAngular 2でコードを書いて動かす
- Angular 2入門 01: 編集ページをつくる
- Angular 2入門 02: リストを加える
- Angular 2入門 03: コンポーネントを分ける
- Angular 2入門 04: サービスをつくる
- Angular 2入門 06: HTTPサービスでデータを取得・保存する
- Angular 2入門 07: HTTPサービスでデータを追加・削除する
- Angular 2入門 08: HTTPサービスが返すObservableを使ったデータの検索
作成者: 野中文雄
作成日: 2017年1月1日
Copyright © 2001-2017 Fumio Nonaka. All rights reserved.