中安拓也のブログ

プログラミングについて書くブログ

AngularでFirebase認証(その3) Firebase Authentication の呼び出し

前回の記事はこちら

引き続き、AngularとFirebaseを使って認証機能を作成していきます。

前回はログイン画面の見た目を作成したので、今回はそのログイン画面にFirebase Authenticationを使って認証機能を導入していきます。

テストアカウントの作成

これから実装するメールアドレスとパスワード認証機能をテストするために、Firebaseのコンソールからアカウントを一人作っておきましょう。

f:id:l08084:20190818191622p:plain
テストアカウントの作成

まずアカウントを追加したいプロジェクトを選択します。

続いてFirebaseコンソールのAuthenticationを選択、メールアドレスとパスワードを入力して「ユーザーを追加」ボタンをクリックするとアカウントが作成されます。

認証機能の実装

ログイン画面のコンポーネントクラスにFirebase Authenticationにメールアドレスとパスワードを渡す処理を書いていきます。

// ...省略
import { AngularFireAuth } from '@angular/fire/auth';
import { Router } from '@angular/router';

// ...省略
export class LoginComponent implements OnInit {
  public loginFormGroup: FormGroup;
  public emailControl: FormControl;
  public passwordControl: FormControl;

  constructor(
    private fb: FormBuilder,
    // 追加
    private afAuth: AngularFireAuth,
    private router: Router
  ) {}

  /**
   * ログインボタン押下時に呼び出し
   *
   */
  public onSubmit() {
    // 下記の処理を追加
    // メールアドレスとパスワードをFirebase Authenticationに渡す
    this.afAuth.auth
      .signInWithEmailAndPassword(
        this.emailControl.value,
        this.passwordControl.value
      )
      // ログインに成功したらホーム画面に遷移する
      .then(user => this.router.navigate(['/home']))
      // ログインに失敗したらエラーメッセージをログ出力
      .catch(error => console.log(error));
  }

// ...省略
}

ログインボタンを押下した時に呼び出されるメソッドにFirebase AuthenticationのsignInWithEmailAndPasswordを追加します。

signInWithEmailAndPasswordの戻り値はPromiseなので、ログイン成功時の処理をthen句、失敗時の処理をcatch句に書いてください。

認証機能を実装するだけならば、この処理を書くだけで完成です。

ログインしてみる

作ったアカウントを使って認証機能を実装できているか検証します。

f:id:l08084:20190818193957p:plain
テストアカウントのメールアドレスとパスワードを入力

f:id:l08084:20190818194126p:plain
ログインに成功

正しいメールアドレスとパスワードを入力するとホーム画面に遷移します。

バージョン情報

  • Angular v7.2.0
  • firebase: v6.3.4
  • Angular Material v7.3.7

AngularでFirebase認証(その2) Angular Materialを使ったログイン画面の作成

前回の記事はこちら

引き続き、AngularとFirebaseを使って認証機能を作成していきます。

前回ではFirebaseコンソールで認証機能を有効化したあと、AngularプロジェクトにAngularFireをインストールするところまで実施しました。

今回の記事では、AppModuleの設定とログイン画面の作成(ガワだけ)までをやっていきます。

Angularプロジェクトのセットアップ

AppModuleのimports:[]に前回編集した設定ファイルとAngularFireを追記します。

  • src/app/app.module.ts
// ...省略
import { AppRoutingModule } from './app-routing.module';

// 設定ファイル
import { environment } from './../environments//environment';

// AngularFire
import { AngularFireModule } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';

@NgModule({
  // ...省略
  imports: [
    // ...省略
    AppRoutingModule,
    // ..追加
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFireAuthModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

ログイン画面の作成

まずログイン画面の見た目から作っていきます。

CSSフレームワークのAngular Materialを使用しました。

  • login.component.html
<app-header></app-header>
<div class="wrapper">
  <mat-card class="login-card">
    <mat-card-header>
      <mat-card-title class="login-title">taikin</mat-card-title>
    </mat-card-header>
    <mat-card-content>
      <form (ngSubmit)="onSubmit()" class="login-form" [formGroup]="loginFormGroup">
        <mat-form-field>
          <input matInput placeholder="email" id="email" formControlName="email" required>
          <mat-error *ngIf="emailControl.invalid">{{getErrorMessageToEmail()}}</mat-error>
        </mat-form-field>

        <mat-form-field>
          <input [type]="hide ? 'password' : 'text'"
            matInput placeholder="password" id="password" formControlName="password" required>
          <mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility' : 'visibility_off'}}</mat-icon>
          <mat-error *ngIf="passwordControl.invalid">{{getErrorMessageToPassword()}}</mat-error>
        </mat-form-field>
        <button type="submit" class="login-button" mat-raised-button [disabled]="!loginFormGroup.valid" color="primary">Login</button>
      </form>
    </mat-card-content>
  </mat-card>
</div>

Angularのリアクティブフォームを採用していて、必須バリデーションとメールアドレス形式のバリデーションをかけています。

  • login.component.ts
import { Component, OnInit } from '@angular/core';
import {
  FormControl,
  Validators,
  FormGroup,
  FormBuilder
} from '@angular/forms';

/**
 * ログイン画面コンポーネント
 *
 * @export
 * @class LoginComponent
 * @implements {OnInit}
 */
@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
  // FormGroup定義
  public loginFormGroup: FormGroup;
  // emailフォームのコントロール定義
  public emailControl: FormControl;
  // passwordフォームのコントロール定義
  public passwordControl: FormControl;

  constructor(private fb: FormBuilder) {}

  public ngOnInit() {
    this.createForm();
    this.emailControl = this.loginFormGroup.get('email') as FormControl;
    this.passwordControl = this.loginFormGroup.get('password') as FormControl;
  }

  /**
   * ログインボタン押下時に呼び出し
   *
   */
  public onSubmit() {
    console.log(this.loginFormGroup.value);
    console.log(this.emailControl.value);
    console.log(this.passwordControl.value);
  }

  /**
   * Eメールフォームにバリデーションエラーメッセージを表示
   *
   */
  public getErrorMessageToEmail() {
    return this.emailControl.hasError('required')
      ? 'You must enter a value'
      : this.emailControl.hasError('email')
      ? 'Not a valid email'
      : '';
  }

  /**
   * パスワードフォームにバリデーションエラーメッセージを表示
   *
   */
  public getErrorMessageToPassword() {
    return this.passwordControl.hasError('required')
      ? 'You must enter a value'
      : '';
  }

  /**
   * フォームの設定
   *
   */
  private createForm() {
    this.loginFormGroup = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required]]
    });
  }
}

バリデーションエラーメッセージを返すメソッドを項目(メールアドレスとパスワード)ごとに定義しているのと、フォームの設定とログイン押下時に呼び出されるメソッドの定義をしています。

  • login.component.scss
.wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 15%;

  .login-card {
    width: 500px;
  }

  .login-form {
    display: flex;
    flex-direction: column;

    .login-button {
      margin-top: 20px;
    }
  }

  .login-title {
    font-weight: bold;
    font-size: 22px;
    color: #3f51b5;
  }
}

レイアウトを定義しているSCSSファイル、やっつけなのでレスポンシブなどの考慮はなし。

f:id:l08084:20190818164109p:plain
ログイン画面

上記のコード(ヘッダーと共通CSSは省略)を実行すると、このようなログイン画面が表示されます。

バージョン情報

  • Angular v7.2.0
  • firebase: v6.3.4
  • Angular Material v7.3.7

次回の記事はこちら

【Angular】[障害メモ]Error: Invalid configuration of route path cannot start with a slash

Angularのルーティング設定モジュールを作成中に下記のエラーが発生した

Error: Invalid configuration of route '/home': path cannot start with a slash
    at validateNode (router.js:613)
    at validateConfig (router.js:577)
    at Router.push../node_modules/@angular/router/fesm5/router.js.Router.resetConfig (router.js:4108)
    at new Router (router.js:3784)
    at setupRouter (router.js:5581)
    at _callFactory (core.js:21292)
    at _createProviderInstance (core.js:21238)
    at initNgModule (core.js:21168)
    at new NgModuleRef_ (core.js:21895)
    at createNgModuleRef (core.js:21884)
(anonymous) @ main.ts:15

エラーメッセージの内容としては、スラッシュ(\)でパスを始めないでね〜って言ってる

修正前のコード

エラーメッセージの通り、{ path: '/home',にスラッシュが入ってしまっているのがエラーの原因となる

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  // pathに'/'が入ってしまっているのがエラーの原因
  { path: '/home', component: HomeComponent }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

修正後のコード

pathプロパティの値からスラッシュを取り除いてあげると、エラーが解消されて正常な画面が表示される

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  // '/'が取り除かれている
  { path: 'home', component: HomeComponent }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

バージョン情報

  • Angular v7.2.0

参考サイト

Angular 2 router Error: Invalid configuration of route 'undefined' - Stack Overflow

AngularでFirebase認証(その1) Firebaseのセットアップ

Angularにメールアドレスとパスワードを使用するFirebase認証を実装していきます。

Firebaseのセットアップ

自分のGoogleアカウントでFirebaseの管理画面にログインして、プロジェクトを作成します。

https://firebase.google.com/?hl=ja

f:id:l08084:20190804162916p:plain
プロジェクトを作成

作成したプロジェクトを選択して、開発メニューの中から「Authentication」をクリックします。

Authentication メニューに移動した後、「ログイン方法を設定」ボタンをクリックしてログイン方法の中から「メール/パスワード」を選択してください。

f:id:l08084:20190804164352p:plain
メール/パスワードを選択

メール/パスワードを設定を有効にした後、保存ボタンを押下します。

Project Overviewに戻って「ウェブ」マークをクリックすると、Angularプロジェクト上で必要になる設定情報が表示されます。

f:id:l08084:20190804165001p:plain
「ウェブ」を選択する

f:id:l08084:20190804170353p:plain
スクリプトが表示される

表示されたFirebase構成オブジェクトをコピーした段階で一旦Firebaseの設定は完了です。

また、Firebase構成オブジェクトは下記の手順でいつでも参照することができます。

f:id:l08084:20190811174927p:plain

f:id:l08084:20190811175050p:plain

Firebase構成オブジェクトを取得したいアプリの設定(歯車アイコン)をクリックします。

設定画面の全般タブ、マイアプリの項目にFirebase構成オブジェクトが表示されています。

続いて、Angularプロジェクトの設定に移ります。

Angularプロジェクトのセットアップ

ターミナルでAngularプロジェクト直下に移動した後、$ npm i firebase @angular/fire コマンドを実行してAngularFireパッケージをインストールします。

$ npm i firebase @angular/fire

続いて、src/enviroments ディレクトリを開いて、FirebaseコンソールからコピーしたFirebase構成オブジェクトをペーストします。

  • src/enviroments/enviroments.ts
// Firebase構成オブジェクトを転記する
export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: 'XXXXXXXXXXXX',
    authDomain: 'XXXXXXXXXXXX',
    databaseURL: 'XXXXXXXXXXXX',
    projectId: 'XXXXXXXXXXXX',
    storageBucket: 'XXXXXXXXXXXX',
    messagingSenderId: 'XXXXXXXXXXXX',
    appId: 'XXXXXXXXXXXX'
  }
};

Firebase構成オブジェクトは認証に関する情報なので、プロジェクトを公開リポジトリにpushするときにはsrc/enviroments/enviroments.ts.gitignoreファイルに追記して公開されないようにすると良いです。

バージョン情報

  • Angular v7.2.0
  • firebase: v6.3.4

参考サイト

GitHub - angular/angularfire: The official Angular library for Firebase.

次回の記事はこちら

【Git】別々のリポジトリ間でmergeを実行する

リポジトリ間でブランチをマージできるということを知らなかったので...メモ

リモートリポジトリとして追加してあげるだけでいいんですね

別のリポジトリにマージするときのコマンド

本記事では、例として下記の条件でマージを実施します

  • Gitリポジトリ https://github.com/l08084/new-repositoryhttps://github.com/l08084/old-repository にマージする
  • マージに使用するブランチは feature/31226 とする
# まず、マージ先リポジトリのプロジェクト配下に移動する
$ cd old-repository

# マージしたいブランチに移動する
$ git checkout feature/31226

# upstreamという名前でマージ先のプロジェクトにマージ元のリモートリポジトリの参照を追加する
$ git remote add upstream https://github.com/l08084/new-repository

# マージ元のリポジトリのブランチの情報などを取得する
$ git fetch upstream

// リポジトリ間のマージが実施される
$ git merge upstream/feature/31226

【ミラーリング】iPhoneの画面をPCのディスプレイで表示する

画面ミラーリングのやり方を知っていると、iOSアプリのプレゼンをする時とかに大きいディスプレイでできて便利です。

ミラーリングの手順

iPhoneの画面を表示したいPCにミラーリングソフトをインストールします。

本記事では、下記のミラーリングソフトの無料版をインストールしています。

ApowerMirror – 画面ミラーリング

ApowerMirror – 画面ミラーリング

  • Apowersoft Limited
  • ユーティリティ
  • 無料
apps.apple.com

続いて、画面を表示したいPCとiPhoneを同じwifiに接続します。

iPhoneの画面を下から上にスワイプしてコントロールセンタを開いて、メニューの中から、画面ミラーリングを選択

f:id:l08084:20190803192441p:plain
画面ミラーリングを選択

同じWifiを使用しているPCが表示されるので、ディスプレイを共有したいPCを選択します。

f:id:l08084:20190803193118p:plain
対象のPCを選択

PCで起動しているApowerMirrorアプリ上にiPhoneの画面が表示(画面ミラーリング)されます。

f:id:l08084:20190803191138p:plain
PC画面にミラーリング成功

バージョン情報

  • iPhone 8 Plus: iOS 12.3.2
  • MacBook Pro: MacOS 10.14.5
  • ApowerMirror: v1.2.6.5

注意点

iPhone(iOS)だと、モバイル端末側にApowerMirrorアプリをインストールする必要はありません。PC側にインストールするだけでOKです

Angularがデータの変更を検知してくれないのでChange Detectionを呼ぶ

障害対応でAngularのChange Detectionをコントロールするクラス(ChangeDetectorRef)を使用する機会があったのでメモ

発生した障害

クリックでインクリメントする値と複数のngIfを組み合わせて、アプリの操作方法を説明するチュートリアルを作成していたが、値は正常にカウントアップされているのにngIf(チュートリアル)が切り替わらない...という障害がときどき発生した

  • ソースのイメージ
<!-- クリックするとチュートリアルが切り替わる -->
<div (click)="nextStep()">
    <!-- stepの値が増えてもずっと、tutorial-1だけが表示される障害 -->
    <div class="common-tutorial-img" >
      <div class="tutorial-1" *ngIf="step === 1"></div>
      <div class="tutorial-2" *ngIf="step === 2"></div>
      <div class="tutorial-3" *ngIf="step === 3"></div>
    </div>
</div>

上記のソースのイメージで、発生した障害を説明するとstep変数は1, 2, 3...といった感じにインクリメントされているのに、ずっと<div class="tutorial-1" *ngIf="step === 1"></div>だけが表示されるといった事象がおきた。

対応内容

step変数の値が変更されているのをAngularが検知して再描画してくれないのが原因だと考えたため、step変数の値が増えるたびにChangeDetectorRef#detectChanges()を毎回呼び出して、モデル(step変数)の値が変更されるたびに画面を再描画するように修正した。

終わりに

step変数の値の渡しかたを工夫してあげれば、ChangeDetectorRef#detectChanges()を呼び出さなくても自動でChange Detectionがはしった気がしないでもない

バージョン

  • Angular v4
  • ionic-angular: 3.9.5

参考サイト

https://angular.io/api/core/ChangeDetectorRef

[Angular] コンポーネントを強制的に再描画する方法 │ Web備忘録

日本語訳:Angular 2 Change Detection Explained - Qiita