AngularのプロジェクトにFirebaseを導入する

> この記事は[Angular+Firebaseでチャットアプリを作る](http://qiita.com/Yamamoto0525/items/a76ea4b3924eeb82b0f9)のエントリーです。
> 前記事：[Firebaseの環境構築](http://qiita.com/Yamamoto0525/items/fb23707831ff6d181544)
> 次記事：[Angular+Cloud Firestore(CREATE, READ, UPDATE, DELETE)を実装する](http://qiita.com/Yamamoto0525/items/c1ec1b7ce2350b294aeb)

# この記事で行うこと

本稿では、Angularのプロジェクトに「angularfire」というライブラリを使ってFirebaseの導入を行います。

- [angularfireとfirebaseをインストール](#angularfireとfirebaseをインストール)
- [Cloud Firestoreを導入する](#cloud-firestoreを導入する)

### angularfireとは

Angularで使い勝手がよくなるようラッピングされた、Firebaseクライアントです。
RxJSを使ってクライアントとFirebase間の同期を行うのが特徴で、現在（2018年9月）のところCloud Firestore、Realtime Database、Authentication、Cloud Storage、Firebase Messaging、Cloud Functions、Analyticsをサポートしています。[公式](https://firebaseopensource.com/projects/angular/angularfire2/#what_is%20angularfire?)でも紹介されています。
**************
（2018/1追記）RTDBの記述を現時点で最新のものに差し替えました。
（2018/9追記）angularfireが対応したのでFirestoreの記述に差し替えました。
（2020/6追記）現時点（2020年6月）での最新の内容に書き換えています。
**************

# 実装内容

## angularfireとfirebaseをインストール

まずはプロジェクトにangularfireとfirebaseをインストールします。

```bash
cd 自分のプロジェクト
ng add @angular/fire@next
Please select a project:
> NgChat (ngchat-xxx)
```

> （2020/06追記）このコマンドはfirabaseの公式とangularfireの公式で異なっていました。テストする際は実行時の[angularfire公式](https://github.com/angular/angularfire/blob/master/docs/install-and-setup.md)を確認してください。


インストールが完了したら、環境設定を行います。
[Firebaseのコンソール](https://console.firebase.google.com/u/0/)を開き、設定＞「全般」タブのなかにあるFirebase SDK snippetから「構成」を選択します。
そこに記載されている構成をコピーし、`/src/environments/environment.ts`を開いて入力します。（<>になっている部分は、自分のapiKeyを入力）

![screenshot-console.firebase.google.com-2020.06.18-17_35_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112566/185c84fa-aaef-e038-55cc-b2807022d6fb.png)


```ts:/src/environments/environment.ts
// <>となっている部分は、自分のapiKeyを入力
export const environment = {
  production: false,
  firebase: {
    apiKey: '<your-key>',
    authDomain: '<your-project-authdomain>',
    databaseURL: '<your-database-URL>',
    projectId: '<your-project-id>',
    storageBucket: '<your-storage-bucket>',
    messagingSenderId: '<your-messaging-sender-id>',
    appId: '<your-app-id>',
    measurementId: '<your-measurement-id>',
  }
};
```

`/src/app/app.module.ts`を開いて、AngularFireModuleとenvironmentをインポートするよう記述します。

```ts:/src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { environment } from '../environments/environment'; // 追加
import { AngularFireModule } from '@angular/fire'; // 追加

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ChatDatePipe } from './pipe/chat-date.pipe';

@NgModule({
  declarations: [
    AppComponent,
    ChatDatePipe
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    NgbModule,
    FormsModule,
    AngularFireModule.initializeApp(environment.firebase) // 追加,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

> ### Angularのモジュール
>
> Angularでは外部ライブラリやコンポーネントなどのクラスを、モジュールで管理しています。
機能単位でモジュールファイルを作成し、`@NgModule`デコレータを使ってその機能モジュールに必要なライブラリモジュールやコンポーネント、サービスなどのクラスを管理しています。
>
> `@NgModule`デコレータには幾つかのプロパティがあり、それぞれの役割がここでわかるようになっています。主なプロパティは次の通りです。
> 
> ```ts
@NgModule({
  // そのモジュールの中で宣言されているディレクティブ（コンポーネント）とパイプを登録する
  declarations: [], 
  // 自分のモジュールに別のモジュールを取り込む
  imports: [],
  // 他のモジュールでimportsに登録されるものを指定
  exports: [],
  // そのモジュールの中で宣言されているサービスを登録する
  providers: [],
  // アプリケーションのエントリポイントになるコンポーネントを指定
  bootstrap: []
})
```

次に本プロジェクトで使用するangularfireの機能モジュールをインポートします。
今回作成するアプリでは認証とデータベースを使用するので、AngularFireAuthModuleとAngularFirestoreModuleをインポートします。

```ts:/src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { environment } from '../environments/environment'; // 追加
import { AngularFireModule } from '@angular/fire'; // 追加
import { AngularFirestoreModule } from '@angular/fire/firestore'; // 追加
import { AngularFireAuthModule } from '@angular/fire/auth'; // 追加

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ChatDatePipe } from './pipe/chat-date.pipe';

@NgModule({
  declarations: [
    AppComponent,
    ChatDatePipe
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    NgbModule,
    FormsModule,
    AngularFireModule.initializeApp(environment.firebase), // 追加,
    AngularFirestoreModule,  // 追加
    AngularFireAuthModule,  // 追加
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

```
これでライブラリの導入は完了です。

## Cloud Firestoreを導入する

ライブラリの導入がうまくいっているか、Cloud Firestoreで試してみます。
まずは`/src/app/app.component.ts`を修正して、AngularFireDatabaseをDI (Dependency Injection)し、このコンポーネントでCloud Firestoreが使えるようにします。

```ts:/src/app/app.component.ts
import { Component } from '@angular/core';
import { Comment, User } from './class/chat';
import { AngularFirestore } from '@angular/fire/firestore'; // 追加
import { Observable } from 'rxjs'; // 追加

export class AppComponent {

  item: Observable<Comment>; // 追加
  public content = '';
  public comments = COMMENTS;
  public current_user = CURRENT_USER;

  // DI（依存性注入する機能を指定）
  constructor(db: AngularFirestore) {
    this.item = db
      .collection('comments')
      .doc<Comment>('item')
      .valueChanges();
  }

  // 新しいコメントを追加
  addComment(comment: string) {
     if (comment) {
       this.comments.push(new Comment(this.current_user, comment));
     }
  }

}
```

次に`/src/app/app.component.html`を修正します。

```html:/src/app/app.component.html
<div class="page">
  <section class="card">
    <div class="card-header">
      NgChat
    </div>
    <div class="card-body">
      <ng-container *ngFor="let comment of comments">
      <div class="media">
        <div class="media-left" *ngIf="comment.user.uid !== currentUser.uid">
          <a href="#" class="icon-rounded">{{comment.initial}}</a>
        </div>
        <div class="media-body">
          <h4 class="media-heading">{{comment.user.name}} Date: {{comment.date | chatDate}}</h4>
          <div>{{comment.content}}</div>
        </div>
        <div class="media-right" *ngIf="comment.user.uid === currentUser.uid">
          <a href="#" class="icon-rounded">{{comment.initial}}</a>
        </div>
      </div>
      <hr>
      </ng-container>
      <!-- Firestoreの反映箇所 -->
      <div class="media">
        <div class="media-left">
          <a href="#" class="icon-rounded">{{(item | async)?.initial}}</a>
        </div>
        <div class="media-body">
          <h4 class="media-heading">{{(item | async)?.user.name}}</h4>
          <div>{{(item | async)?.content}}</div>
        </div>
      </div>
    </div>
  </section>

  <section>
    <form class="chart-form" (submit)="addComment(content)">
      <div class="input-group">
        <input type="text" class="form-control"
               [(ngModel)]="content"
               name="comment"
               placeholder="Comment" />
        <div class="input-group-append">
          <button class="btn btn-info" type="submit">SEND</button>
        </div>
      </div>
    </form>
  </section>
</div>
```
> `item`のデータ読み込みの際に、`async`というPipeが使われています。
> これはビューへ非同期で読み込む際に使用するPipeです。ここではAngularに採用されているRxjsというライブラリのObservableが利用されていますが、その詳細については別の記事で解説します。

これでクライアント側の準備が整いました。
`ng serve`を使ってプロジェクトを実行すると、エラーログがコンソールに出力されます。

```bash
ERROR in The target entry-point "@angular/fire" has missing dependencies:
 - firebase/app
```

2020年6月時点で、```ng add @angular/fire@next```だけでは十分ではなく、これとは別にfirebaseのライブラリをインストールしておく必要があります。

```bash
 npm install firebase --save
```

後はCloud Firestoreにデータを登録するだけです。
まずはデータの親となるcollection「comments」を登録します。

![screenshot-console.firebase.google.com-2020.06.23-00_16_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112566/d10f2b98-6bf7-37f4-0665-05d023014e5e.png)

次に子となる「item」を登録します。

![screenshot-console.firebase.google.com-2020.06.23-00_19_43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112566/f52f017d-7bf2-e6a7-e7eb-864fad69c22f.png)

実行結果を確認できるようにするため、すべての書き込み、読み込みを許可できるようにルールを変更します。

![screenshot-console.firebase.google.com-2020.06.23-00_27_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112566/be3ef031-7797-eae3-b29c-557c33d63b2c.png)


### 実行結果

![Jun-23-2020 00-46-43.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112566/c13c8e1c-78c3-79d9-3190-6e2ceda91c32.gif)

データベースの更新がリアルタイムでチャットに反映されるのが確認できました。
次はCloud FirestoreのCRUD（Create、Read、Update、Delete）の実装をしていきます。

### ソースコード

この時点での[ソースコード](https://github.com/Yamamoto0525/NgChat/tree/a7ef9df34c6f5852d313bc5542e96f1660251029)
※apiKeyは削除しているので、試すときは自身で作成したapiKeyを入れてください。