Angular９とFirebaseでブログを作ってみる４（画面遷移をできるようにしてみる）

[前回](https://qiita.com/teracy55/items/ac9f83c1fcac589b876b)、TOPページをいい感じにしました。
今回は他の画面を作って、画面遷移できるようにしてみます。

# ブログ一覧／詳細ページの追加

ブログの画面は以下のようなURLになるように作りたいと思います。

一覧：/blogs
詳細：/blogs/:id

## サービスの作成

画面を作る前に表示用のデータを取得用サービスを作ります。
ただ、今回はまだFirebaseからデータを取得しないので、ダミーデータを返すようにします。

```src/app/blogs/blogs.service.ts
import { Injectable } from '@angular/core';
import { Blog } from 'src/types/blog.interface';

@Injectable()
export class BlogsService {
  readonly testData: Blog[] = [
    { id: 1, title: 'ブログ１', contents: 'ブログ１のコンテンツ', tags: ['タグ1'], created_at: '2020/01/01 00:00:00' },
    { id: 2, title: 'ブログ２', contents: 'ブログ２のコンテンツ', tags: ['タグ2', 'タグ3'], created_at: '2020/01/02 00:00:00' },
  ];

  constructor() { }

  async getBlogs() {
    return this.testData;
  }

  async getBlog(id: number) {
    return this.testData.find(data => data.id === id);
  }
}

```

- getBlogs：ブログ一覧を返却
- getBlog：指定したIDを持つブログを返却

※後々ちゃんとFirebaseから取得するようにします（それを想定してasyncつけてます）


ちなみにBlog型は以下のように定義してます

```src/types/blog.interface.ts
export interface Blog {
    id: number;
    title: string;
    contents: string;
    tags: string[];
    created_at: string;
}
```


## ブログ一覧画面

- 一覧表示用のコンポーネントを作成
    - コンストラクタでサービスをDI
    - getBlogsで取得した一覧情報をblogsに設定

```src/app/blogs/list/list.component.ts
import { Component, OnInit } from '@angular/core';
import { BlogsService } from '../blogs.service';
import { Blog } from 'src/types/blog.interface';

@Component({
  selector: 'app-blogs-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.scss']
})
export class BlogsListComponent implements OnInit {
  blogs: Blog[];

  constructor(private blogsService: BlogsService) { }

  async ngOnInit() {
    this.blogs = await this.blogsService.getBlogs();
  }
}
```

- HTMLを作成
    - コンポーネント側でblogsに一覧を設定するため、項目数分だけngForでループを回す
    - 作成日はパイプを使って`yyyy/MM/dd`形式で出力
    - ブログタイトルはリンクを表示（表示しているブログIDをリンク先に指定）

```src/app/blogs/list/list.component.html
<div class="blog-list">
    <h1 class="title">ブログ一覧</h1>
    <ul *ngIf="blogs">
        <li *ngFor="let blog of blogs">
            <span class="create-date">{{blog.created_at | date: 'yyyy/MM/dd'}}</span>
            <a class="blog-title" routerLink="/blogs/{{blog.id}}">{{blog.title}}</a>
        </li>
    </ul>
</div>
```

## 詳細画面の追加

- コンポーネントの作成
    - コンストラクタでサービスをDI
    - URLのパスからブログIDを取得するためActivatedRouteをDI
    - `this.activatedRoute.snapshot.params.id`でパスからIDを取得
    - getBlogでIDで指定された内容を取得
        - params.idはstringのため、Number(id)で数値型に変換

```src/app/blogs/detail/detail.component.ts
import { Component, OnInit } from '@angular/core';
import { BlogsService } from '../blogs.service';
import { ActivatedRoute } from '@angular/router';
import { Blog } from 'src/types/blog.interface';

@Component({
  selector: 'app-blogs-detail',
  templateUrl: './detail.component.html',
  styleUrls: ['./detail.component.scss']
})
export class BlogsDetailComponent implements OnInit {
  detail: Blog;

  constructor(private blogsService: BlogsService, private activatedRoute: ActivatedRoute) { }

  async ngOnInit() {
    const id = this.activatedRoute.snapshot.params.id;
    if (id) {
      this.detail = await this.blogsService.getBlog(Number(id));
    }
  }
}
```

```src/app/blogs/detail/detail.component.html
<div *ngIf="detail" class="blog-detail">
    <h1>{{detail.title}}</h1>
    <span>{{detail.contents}}</span>
</div>
```


## /blogsルーティング設定

遅延ロードによるモジュール読み込みを行えるようなモジュール構成にしていきます

- `/blogs`のサブパスでモジュールを切るため、BlogsRoutingModuleを作成
    - 詳細ページは`:id`で動的にブログIDを指定できるようにします

```src/app/blogs/blogs-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BlogsListComponent } from './list/list.component';
import { BlogsDetailComponent } from './detail/detail.component';

const routes: Routes = [
  { path: '', component: BlogsListComponent },
  { path: ':id', component: BlogsDetailComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class BlogsRoutingModule { }
```

- BlogsModuleを作成し、一覧／詳細コンポーネントの宣言、BlogsRoutingModuleのインポートを行います

```src/app/blogs/blogs.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BlogsRoutingModule } from './blogs-routing.module';
import { BlogsService } from './blogs.service';
import { BlogsListComponent } from './list/list.component';
import { BlogsDetailComponent } from './detail/detail.component';

@NgModule({
  declarations: [
    BlogsListComponent,
    BlogsDetailComponent,
  ],
  imports: [
    CommonModule,
    BlogsRoutingModule,
  ],
  providers: [
    BlogsService,
  ]
})
export class BlogsModule { }
```

- `app-routing.module.ts`でloadChildrenを用いてBlogsModuleを遅延ロードするようにします
    - こうすることで、/blogsにアクセスしたときにはじめてBlogsModuleが読み込まれるようになるため、その分初期表示時の負荷が軽減できます。
    - また、Moduleのインポートが不要のため、blogsディレクトリ内でコードが完結できるという点もGoodだと思います。

```src/app/app-routing.module.ts
・・・

const routes: Routes = [
  ・・・
  { path: 'blogs', loadChildren: () => import('./blogs/blogs.module').then(m => m.BlogsModule) },
  ・・・
];

・・・
```



# こんな感じになりました

## ブログ一覧

`/blogs`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325108/21300956-2a78-0c35-482d-c21cbd61d5f2.png)


## ブログ詳細

`/blogs/1`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325108/c0fafb40-3662-0a9d-c554-777f7aa44ea2.png)


## 自己紹介ページ

ちなみに、自己紹介ページも雑に作りました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325108/02bbb5ab-79a1-59b8-f47a-c9833c57e5c0.png)



# まとめ

だいぶブログの体をなしてきた気がしてきました！

次回はFirestoreからデータをとってきて、画面に出力するところをやってみようと思います。


（追記）
[Angular９とFirebaseでブログを作ってみる５（Firestoreからデータを取得して表示してみる）](https://qiita.com/teracy55/items/517b2e88b4850312e8f6)