AWS Amplify+Angular6+Cognitoでログインページを作ってみる ~フロントエンド編②~

本ブログではAWS Amplify+Angular6+Cognitoでログインページを作っていきます。また、ついでに、API GatewayにもCognitoで認証をかけて、Cognitoでログインしているユーザのみ利用できるようにしていきます。
2018.06.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも!大阪オフィスの西村祐二です。

本ブログは下記の続きになります。

AWS Amplify+Angular6+Cognitoでログインページを作ってみる ~フロントエンド編①~

ゴールとして下記動画のようなサイトを構築していきます。

前回、Angular6とAWS-Amplifyを使って、設定周りと、外部と通信するところのロジック部分の実装を行いました。

今回は、画面を構成するコンポーネントの実装を行っていきます。

コンポーネントとしては下記を作成していきます。

  • ログイン画面
  • サインアップ画面
  • ペット情報取得、一覧表示画面(ホーム画面に配置)
  • ホーム画面

ログイン画面

LoginComponent

ログイン画面のコンポーネントを作成します。

$ ng g component component/login
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from './../../auth/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  public loginForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private router: Router,
    private auth: AuthService
  ) {}

  ngOnInit() {
    this.initForm();
  }

  initForm() {
    this.loginForm = this.fb.group({
      email: ['', Validators.required],
      password: ['', Validators.required]
    });
  }

  onSubmitLogin(value: any) {
    const email = value.email,
      password = value.password;
    this.auth.signIn(email, password).subscribe(
      result => {
        this.router.navigate(['/']);
      },
      error => {
        console.log(error);
      }
    );
  }
}
  • initForm()
    • メールアドレス、パスワードを入力するための入力フォームの設定を行っています。初期値、バリデーションなども行うことができます。今回、requiredとしているので、入力必須としています。
  • onSubmitLogin(value: any)
    • ログインフォームがサブミットされる際のイベントハンドラです。AuthServicesignIn()メソッドの結果を受けて、ログインに成功したら['/']に遷移する設定となっています。が、ルーティングの設定(app-routing.module.ts)でhomeへリダイレクトされます。

サインアップ画面

SignupComponent

サインアップ(登録)画面のコンポーネントを作成します。 このページでは、登録情報の入力と、確認コードの検証を行います。

$ ng g component component/signup
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from './../../auth/auth.service';

@Component({
  selector: 'app-signup',
  templateUrl: './signup.component.html',
  styleUrls: ['./signup.component.css']
})
export class SignupComponent implements OnInit {
  public signupForm: FormGroup;
  public confirmationForm: FormGroup;
  public successfullySignup: boolean;

  constructor(
    private fb: FormBuilder,
    private router: Router,
    private auth: AuthService
  ) {}

  ngOnInit() {
    this.initForm();
  }

  initForm() {
    this.signupForm = this.fb.group({
      email: ['', Validators.required],
      password: ['', Validators.required]
    });
    this.confirmationForm = this.fb.group({
      email: ['', Validators.required],
      confirmationCode: ['', Validators.required]
    });
  }

  onSubmitSignup(value: any) {
    const email = value.email,
      password = value.password;
    this.auth.signUp(email, password).subscribe(
      result => {
        this.successfullySignup = true;
      },
      error => {
        console.log(error);
      }
    );
  }

  onSubmitConfirmation(value: any) {
    const email = value.email,
      confirmationCode = value.confirmationCode;
    this.auth.confirmSignUp(email, confirmationCode).subscribe(
      result => {
        this.auth.signIn(email, this.auth.password).subscribe(
          () => {
            this.router.navigate(['/']);
          },
          error => {
            console.log(error);
            this.router.navigate(['/login']);
          }
        );
      },
      error => {
        console.log(error);
      }
    );
  }
}
  • onSubmitSignup()
    • サインアップフォームがサブミットされる際のイベントハンドラ。AuthServicesignUp()メソッドの結果、サインアップに成功したら、successfullySignupフラグをtrueにし、検証コードの入力フォームを表示しています。
  • onSubmitConfirmation()
    • 検証コードの入力フォームがサブミットされる際のイベントハンドラ。AuthServiceconfirmSignUp()メソッドの結果、サインアップに成功したらログインを行い、home画面へ遷移します。

ペット情報取得、一覧表示画面

PetComponent

ペット情報取得、一覧表示画面を作成します。 このページでは、APIを叩いてペット情報を取得、取得した情報を画面に表示します。

petクラスを作成し、APIから取得したデータをとる変数petsDataにPet型として設定します。

$ ng g class component/pet

pet.ts

export class Pet {
  id: number;
  type: string;
  price: number;
}

次にのコンポーネントを作成します。

$ ng g component component/pet
import { Component, OnInit } from '@angular/core';
import { PetService } from './../../pet/pet.service';
import { AuthService } from './../../auth/auth.service';
import { Pet } from './pet';

@Component({
  selector: 'app-pet',
  templateUrl: './pet.component.html',
  styleUrls: ['./pet.component.css']
})
export class PetComponent implements OnInit {
  private token: string;
  petsData: Pet[];
  constructor(private petService: PetService, private auth: AuthService) {}

  ngOnInit() {
    this.token = this.auth.getIdToken();
  }

  Pet(): void {
    this.petService.getPets(this.token).subscribe(result => {
      this.petsData = result;
      console.log(result);
    });
  }
}
  • this.token = this.auth.getIdToken();
    • HTTPリクエストの際にトークンをヘッダに付与するためにauth.getIdToken()から取得しています。
  • Pet()
    • HTTPリクエストするためボタンをクリックしたときのイベントハンドラ。petService.getPets(this.token)メソッドを実行し、その結果をpetsDataに格納します。

ホーム画面

HomeComponent

ログイン済みのユーザーのみが閲覧出来るページのコンポーネントを作成します。

$ ng g component component/home
import { Component, OnInit } from '@angular/core';
import { AuthService } from './../../auth/auth.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  username: String;
  constructor(private auth: AuthService) {}

  ngOnInit() {
    this.getData();
  }
  getData(): void {
    this.auth.getData().subscribe(
      result => {
        console.log(result);
        this.username = result.username;
      },
      error => {
        console.log(error);
      }
    );
  }
}
  • getData()
    • ログイン時にHomeの画面上にログインしたユーザ名を表示するために、auth.getData()メソッドを実行し、その結果をusernameに追加しています。

ナビバー

AppComponent

AppComponentには各ページ共通で表示するナビゲーションを配置しました。

import { Component, OnInit, OnDestroy, AfterViewChecked, ChangeDetectorRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { AuthService } from './auth/auth.service';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy, AfterViewChecked {
  subscription: Subscription;
  username: String;
  loggedIn: boolean;

  constructor(public auth: AuthService, private cdr: ChangeDetectorRef) {
    this.username = localStorage.getItem(
      environment.localstorageBaseKey + 'LastAuthUser'
    );
  }

  ngOnInit() {
    this.subscription = this.auth.isAuthenticated().subscribe(result => {
      this.loggedIn = result;
    });
  }
  ngAfterViewChecked() {
    this.username = localStorage.getItem(
      environment.localstorageBaseKey + 'LastAuthUser'
    );
    this.cdr.detectChanges();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

  onClickLogout() {
    this.auth.signOut();
  }
}
  • ナビゲーションのログイン・ログアウト
    • AuthServiceのloggedInSubjectをasyncパイプで受け取って、表示する内容を変更しています。
  • ngOnInit()
    • ページがリロードされた際にログイン状態を再取得するためにAuthServiceのisAuthenticated()メソッドを呼び出しています。
  • ngAfterViewChecked()
    • 子コンポーネントの読み込みが完了したあとに実行されるメソッドです。ローカルストレージからusernameを取得します。だた、現状ExpressionChangedAfterItHasBeenCheckedErrorがでます。今度修正したいと思います。
    • => ChangeDetectorRefを使うことでエラーを回避できました。
  • onClickLogout()
    • ログインボタンが押された際のイベントハンドラ。

AppModule

最終的なsrc/app/app.module.tsは次のようになりました。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';

import { LoginComponent } from './component/login/login.component';
import { SignupComponent } from './component/signup/signup.component';
import { HomeComponent } from './component/home/home.component';
import { PetComponent } from './component/pet/pet.component';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    SignupComponent,
    HomeComponent,
    PetComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    ReactiveFormsModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

さいごに

いかがだったでしょうか。

画面を構成するコンポーネント部分の実装を行いました。

次はBootstrap4を使ってスタイリングをやっていきます。

参考サイト

AWS Amplify + AngularでサーバーレスSPAの認証をするサンプル