Angularヒーローチュートリアル6

# ルーティング
URLによってどのコンポーネントを表示するか決められるようにします。

## ルーティングファイル
まだ`app-routing.module.ts`が`src/app`下に存在しない場合は、ファイルを作成します。

```terminal:ターミナル
$ cd src/app
$ ng generate module app-routing --flat --module=app
```
そして作成されたtsファイルにコンポーネント情報を追加します。

```ts:app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HeroesComponent} from './heroes/heroes.component';//ここを追加


const routes: Routes = [
  {path: 'heroes', component:HeroesComponent}//ここを追加
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```
`{path: 'heroes', component:HeroesComponent}`は、`path:`がURLの文字列で`component:`が表示するコンポーネントです。

## ルーティングによって表示が変わる部分
表示する場所を決定します。

```html:app.component.html
<h1>{{title}}</h1>
<nav>
  <a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-message></app-message>
```
下から2行目の`<router-outlet></router-outlet>`の部分にルーティングファイルで設定したコンポーネントが表示されます。そしてaタグで囲まれたHeroesを押すとheroesコンポーネントを表示します。また、aタグの中の`routerLink`がURLを指定しています。

## ダッシュボードコンポーネント
よりルーティングがわかりやすくなるように、新しくダッシュボードを表示するコンポーネントを作成します。

```terminal:ターミナル
$ cd src/app
$ ng generate component dashboard
```
ルーティングの理解を目的としているので、ts・html・cssをコピペで完成です。

```ts:dasboard.component.ts
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: [ './dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit() {
    this.getHeroes();
  }

  getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  }
}
```

```html:dashboard.component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <a *ngFor="let hero of heroes" class="col-1-4">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </a>
</div>
```

```css:dashboard.component.css
/* DashboardComponent's private CSS styles */
[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: #3f525c;
    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;
    }
  }
```

そしてルーティングファイルにダッシュボードのルートを追加します。

```ts:app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HeroesComponent} from './heroes/heroes.component';
import {DashboardComponent} from './dashboard/dashboard.component'


const routes: Routes = [
  {path: 'heroes', component:HeroesComponent},
  {path: 'dashboard', component:DashboardComponent}//ここを追加
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

```

ダッシュボードを表示するボタンを作ります。

```html:app.component.html
<h1>{{title}}</h1>
<nav>
    <a routerLink="/dashboard">Dashboard</a>
    <a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-message></app-message>
```

localhostを開き作成したDashboardボタンを押すと以下のような画面が出てきます。

<img width="1435" alt="スクリーンショット 2020-02-05 0.35.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519379/790b3b55-ddde-84fe-4263-3068a662620b.png">

## デフォルトルート

URLに特別な文字列がついてなく一番最初の検索のままのとき、今の設定では何のコンポーネントも表示されません。
このデフォルト状態でも指定のコンポーネントが表示されるようにします。

```ts:app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HeroesComponent} from './heroes/heroes.component';
import {DashboardComponent} from './dashboard/dashboard.component'


const routes: Routes = [
  {path:'', redirectTo:'/dashboard', pathMatch:'full'},//ここを追加
  {path: 'heroes', component:HeroesComponent},
  {path: 'dashboard', component:DashboardComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

```
これで`localhost:4200`を開いてみると、ダッシュボードが表示され、URLが`localhost:4200/dashboard`に自動で変わっています。

## ディープリンク
ヒーローの詳細をルーティングによって表示します。

まず、ルーティングから加えていきます。

```ts:app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HeroesComponent} from './heroes/heroes.component';
import {DashboardComponent} from './dashboard/dashboard.component';
import {HeroDetailComponent} from './hero-detail/hero-detail.component';//ここを追加


const routes: Routes = [
  {path:'', redirectTo:'/dashboard', pathMatch:'full'},
  {path: 'heroes', component:HeroesComponent},
  {path: 'dashboard', component:DashboardComponent},
  {path: 'detail/:id', component:HeroDetailComponent}//ここを追加
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

```
`:id`は引数です。ヒーローのidが入ります。

今まではクリックしたヒーローの情報を`onSelect`で保存して詳細を表示していましたが、今回はURLの情報からヒーローの詳細を探して表示します。そのためdetaiのtsファイルを大幅に変更します。それに伴って、サービスにヒーローを探す関数を加えます。

サービスではidからヒーローの情報を探す関数を作ります。

```ts:hero.service.ts
  getHero(id: number): Observable<Hero>{//追加部分のみ
    this.messageService.add(`HeroService: fetched hero id=${id}`);
    return of(HEROES.find(hero => hero.id === id));
  }
}
```
サービスで作った関数を使って`hero`にヒーローの詳細を代入します。

```ts:hero-detail.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';//ここを追加
import { Location } from '@angular/common';//ここを追加

import {Hero} from '../hero';
import { HeroService }  from '../hero.service';//ここを追加

@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit {

  hero : Hero;//ここを変更

  constructor(
    //ここを追加
    private route: ActivatedRoute,
    private heroService: HeroService,
    private location: Location
  ) { }

  ngOnInit() {
    this.getHero();//ここを追加
  }
  //ここを追加
  getHero(): void{
    const id = +this.route.snapshot.paramMap.get('id');
    this.heroService.getHero(id).subscribe(hero => this.hero = hero);
  }
}
```
ここで`Location`をインポートしましたが、あとで使う為今は気にしなくて大丈夫です。
`id = +this.route.snapshot.paramMap.get('id');`は、URLに書かれたidを受け取っています。URLは全て文字列で表されます。その為、`+`で文字列を数字に型変換しています。

いらなくなったコードは削除してきれいに整頓します。

```ts:hero.component.ts
import { Component, OnInit } from '@angular/core';
import {Hero} from '../hero';
import {HeroService} from '../hero.service';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {

  heroes: Hero[];

  constructor(private heroService: HeroService) { }

  ngOnInit() {
    this.getHeroes();
  }

  getHeroes(): void{
    this.heroService.getHeroes().subscribe(
      heroes => this.heroes = heroes
    );
  }
}
```

そして、リストやダッシュボードをクリックしたら詳細が表示されるようにHTMLを変更していきます。

```html:hero.compomnent.html
<h2>My Heroes</h2><img width="347" alt="スクリーンショット 2020-02-05 3.33.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519379/ae8a8a7f-615a-8e49-537e-1ef2f19c451e.png">

<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <a routerLink="/detail/{{hero.id}}">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </a>
  </li>
</ul>
```

```html:dashboard.component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <a *ngFor="let hero of heroes" class="col-1-4" routerLink="/detail/{{hero.id}}">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </a>
</div>
```
<img width="347" alt="スクリーンショット 2020-02-05 3.33.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519379/be452983-597a-0e1a-00bb-79d183cc4fc5.png">

このように詳細とメッセージだけが出力されます。

## 戻る機能
リストから来たのか、ダッシュボードから来たのか判断して戻る機能を詳細画面に追加します。`location`を使うとすごい簡単です。

```ts:hero-detail.component.ts
  goBack(): void{//追加部分のみ
    this.location.back();
  }
```

```html:hero-detail.component.html
<div *ngIf="hero">

    <h2>{{hero.name | uppercase}} Details</h2>
    <div><span>id: </span>{{hero.id}}</div>
    <div>
      <label>name:
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </label>
    </div>
    <button (click)="goBack()">go back</button>
</div>
```
<img width="352" alt="スクリーンショット 2020-02-05 3.44.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519379/0d0277da-2556-e464-ff91-a4f3162e28f6.png">
gobackボタンができ、押すと戻ります。

## CSSコピペ
きれいに整えます。
『最後のコード・レビュー』を参考にします。
>http://xn--angular-ii4f.jp/tutorial/toh-pt5
>angular公式チュートリアル(ルーティング編)