Angular+FirebaseでAngularチュートリアルアプリにFirebaseを対応させる(「20200307 Angular+Firebase Live Coding Session #gdgtokyo」のまとめ)

# What
この記事は, 3/7(Sat) GDG Tokyo様主催の[Angular+Firebase Live Coding Session]で扱われたHands-onをまとめる.
Hands-onでは, Angularチュートリアルである[スマホ決済ECサイト]に対して, Firebaseを対応させ, アプリのホスティング, FirestoreのDB使用, FirebaseAuthによる認証, FirebaseRemoteConfigによるアプリリリース不要のアプリ文言変更などに対応させた.

# Todo
1. FirebaseのHostingを使ってアプリをデプロイする
2. FirebaseのFirestoreを使ってFirebaseのDatabaseをリアルタイムでアプリに表示する
3. FirebaseのAuthenticationを使ってGoogle Sign Inを実装する
4. FirebaseのAuthenticationを使って非ログイン/ログイン状態の不整合をリダイレクトで処理する
5. FirebaseのRemoteConfigを使ってリリース不要でアプリの表示文言を変更する
(RemoteConfigのWebアプリ版はbeta版)

# What is this for
この記事は, 以下開催案内の記述の通りAngularとFirebase入門向け
connpass URL:
https://gdg-tokyo.connpass.com/event/168843/?utm_campaign=event_message_to_selected_participant&utm_source=notifications&utm_medium=email&utm_content=title_link
> こちらはAngularとFirebaseの入門向けLive Coding Sessionとなります。

# What use
扱うF/W, 機能は以下の通り.

- Angular

- Firebase
    - Firestore
    - Authentication
    - Hosting
    - RemoteConfig

# Environment & Version
- OS: Windows10
- Angular CLI: 8.3.18
- Node: 10.16.3

# Note
実装の大まかな流れ
1. Angularプロジェクト生成
2. Firebaseプロジェクト生成
2. Firebase Consoleで機能を使えるようにする
3. Angularアプリで追加したFirebaseの機能を使えるようにする(app.component.tsを編集)
4. 各Component(≒ページ)のTS/HTMLを記述する
5. 2~4の繰り返し
 
# Step for reproducing
### Hand-onのarchive動画は以下
https://youtu.be/zeS5kfdrSxQ?t=7196

## 初期構築
 1. Angular CLI/プロジェクトの生成(下記参照)
    - https://angular.jp/guide/setup-local#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6
 2. Angularチュートリアル完成状態のソースをcloneする(lacolaco様のgithub)

        git clone github.com/lacolaco/angularfire-20200307-starter
        (cd "project directory"が必要かも)
        npm install
        npm run start

 3. Firebaseでプロジェクトを作る
    - https://console.firebase.google.com/
 4. FirebaseプロジェクトにWebアプリを追加
 5. AngularアプリとFirebaseプロジェクトを紐づける(environmentにfirebaseConfig追加)
   - Firebase Console -> メニューバーギアマーク -> プロジェクトを設定 -> 全般タブ -> マイアプリ -> Firebase SDK snippet -> 構成のsnippetをコピー
   - 以下に貼り付け

```/src/environments/environment.ts
const firebaseConfig = {
  apiKey: ~~~
  ~~~中略~~~
  measurementId: ~~~
};
```

environment定数に1行追記

```/src/environments/environment.ts
export const environment = {
  production: false,
  firebaseConfig //←追記
};
```

## FirebaseHostingでアプリをHostingする
 1. FirebaseでHosting設定をする
   - Firebase Console - HostingからHostingするアプリを選択
 2. 以下コマンドでAngularアプリをHostingする

   ```
   npm run ng deploy
   ```
###  🎉Hosting完了🎉 (1/5完了)

## Firestoreを使ってFirebase DatabaseからAngularアプリにデータを渡す
#### cloneしたプロジェクトの製品(Phone XLなど)は, /src/app/product.tsを読み込んでいるだけ. Firestoreのデータベースのデータを表示するように実装する
1. FirebaseプロジェクトでDatabaseを以下のように作成/設定する
    - コレクション: "products"
    - ドキュメント: 自動IDでよい
    - フィールド(/src/product.tsを複製する ※)
        - name(string),
        - price(number)
        - desctiprion(string)
        - id(number)

    ※注意※
    idはproducts.tsにないプロパティだが, 後にngForでコレクションをループしてhtmlの要素を生成するためにiterableオブジェクトが必要になるため必須

2. AngularでFirestoreを利用できるようにする
    - "AngularFireModule" と "AngularFirestoreModule"をimportする.
    - 'fire/firestore';は現時点では書かないといけないらしい.

    ```/src/app/app.component.ts
    import { AngularFireModule } from '@angular/fire';
    import { AngularFirestoreModule } from '@angular/fire/firestore';
    import 'fire/firestore';
    ```


3. @NgModuleにimportした2つのモジュールを登録する

    ```/src/app/app.component.ts
    RouterModule.forRoot([
     ~~~中略~~~
    ]),
    AngularFireModule.initializeApp(environment.firebaseConfig), //←追記
    AngularFirebaseModule, //←追記
    ```

 4.AngularFirestoreをproduct-list.componentにDependency Injectionする
- また, "valueChanges"メソッドにより, Firestoreに変更があったときにリアルタイムでアプリが変更を検知し更新するようにする.

```/src/app/product-list.component.ts
constructor(private firestore: AngularFirestore) {}
products = this.firestore.collection('products').valueChanges();
```

### 🎉 Firestoreで変更するたびにアプリの製品情報が更新されるようになる 🎉 (2/5完了)

## FirebaseAuthでGoogle認証を実装する
 1. FirebaseプロジェクトにFirebaseAuthでGoogleAuthProviderを使えるように設定する
  - Firebase Console - Authentication - Sign-in methodタブ - Google - 有効
 2. AngularアプリでFirebaseAuthを利用できるようにする

    ```/src/app/app.component.ts
    import { AngularFireAuthModule } from '@angular/fire/auth'; //←追記
    ~~~中略~~~
    AngularFirestoreModule,
    AngularFireAuthModule, //←追記
    ```

3.'/sign-in'ページを追加するために, sign-in componentを生成する

    ```
    ng generate conponent sign-in
    ```

4.アプリのrouting(ページ遷移の設定)の設定をする.

```/src/app/app.component.ts
    RouterModule.forRoot([
      { path: '', component: ProductListComponent },
      { path: 'products/:productId', component: ProductDetailsComponent },
      { path: 'cart', component: CartComponent },
      { path: 'shipping', component: ShippingComponent },
      { path: 'sign-in', component: SignInComponent }, //←追記
```

ブラウザでlocalhost:4200/sign-inにアクセス
#### sign-inページが works!

5.signInメソッド追加

```/src/app/sign-in.component.ts
  ngOnInit(): void {
  }

//↓追記
  signIn() {
    this.auth.signInWithPopup(new auth.GoogleAuthProvider());
  }

```

6.sign-in状態のhtmlやbuttonを実装

```/src/app/sign-in.component.html
<div *ngIf="user | async as u; else showSignIn">
  Logged in
  {{ u.displayName }}

  <a routerLink="/cart">Go to cart</a>
</div>

<ng-template #showSignIn>
  <button (click)="signIn()">Sign in with Google</button>
</ng-template>
```

7.sign out機能をtop-barに実装する
(一番下にbutton要素を追加)

```/src/app/top-bar.component.html
<button *ngIf="user | async" (click)="signOut()">
  Sign out
</button>
```

### 🎉 Googleサインインが実装完了 🎉 (3/5完了)

## FirebaseAuthGuardで非ログイン/ログイン状態での不整合を防ぐ
- 非ログイン状態で/cartページに遷移したら, /sign-inページにリダイレクトさせる
- ログイン状態で/sign-inページに遷移したら, /ページにリダイレクトさせる

1. モジュールの追加

```/src/app/app.component.ts
import { 
  AngularFireAuthGuardModule,
  canActivate,
  redirectLoggedInTo,
  redirectUnauthorizedTo
} from '@angular/fire/auth-guard';
```

2.routingの設定

```/src/app/app.component.ts
    RouterModule.forRoot([
      { path: '', component: ProductListComponent },
      { path: 'products/:productId', component: ProductDetailsComponent },
//↓cartページにcanActivate関数追加
      {
        path: 'cart',
        component: CartComponent,
        ...canActivate(() => redirectUnauthorizedTo(['/sign-in']))
      },
      { path: 'shipping', component: ShippingComponent },
//↓sign-inページにcanActivate関数追加
      {
        path: 'sign-in',
        component: SignInComponent,
        ...canActivate(() => redirectLoggedInTo(['/']))
      },
    ]),

```

- canActivate関数の書き方はAngularっぽい書き方らしい

### 🎉 非ログイン状態で/cartページ遷移時に/sign-inページ遷移, ログイン状態で/sign-inページに遷移時に/(ルートページ)に遷移するようになる 🎉 (4/5完了)

## FirebaseRemoteConfigでアプリのリリースなく, アプリ内の文言を変更する
### (RemoteConfigのWebアプリ版はbeta版)
1. FirebaseでRemoteConfigを設定する

- Firebase Console - Remote Config(下の方) - パラメータを追加 - 任意のパラメータキー - 値を入力(ex)Now on sale! - 条件の値を追加 - (ex)言語 - Japaneseの場合セール中です


2.モジュール追加

```/src/app/app.component.ts
import {
  AngularFireRemoteConfigModule,
  SETTHINGS
} from '@angular/fire/remote-config';
```

- SETTINGSは開発用にRemoteConfigのFetch間隔を指定するために使用. 指定しない場合のFetch間隔は長いため, Fetch間隔を任意に指定できるようにする.


3.providersにSETTING定数などを記載

```/scr/app/app.component.ts
  providers: [
    {
      provide: SETTINGS, //←追記
      useValue: { minimumFetchIntervalMillis: 10_000 } //←追記
    }
  ],
```

4.プロモーションメッセージを表示するcomponentをgenerateする

```
ng generate component promotion-message
```

5.モジュール追加

```/src/app/app.component.ts
AngularFireAuthGuardModule,
AngularFireRemoteConfigModule, //←追記
```

6.promotion-message側の実装

```/src/app/promotion-message.component.ts
constructor(private config: AngularFireRemoteConfig) {}

promotionMessage = this.config.strings['promotionMessage'];
```

7.テンプレート側の実装

```/src/app/promotion-message.component.html
<p *ngIf="promotionMessage | async as message">{{ message }}></p>
```

### 🎉 RemoteConfigで設定した文字列がアプリ内で表示される 🎉 (5/5完了)

## 最終的なコード
https://github.com/lacolaco/angularfire-20200307-starter
(lacolaco様)のfinishedブランチを参考にしました.