#完成イメージ
こんな感じ。 pic.twitter.com/CvOFjFq2th
— げん げんと (@gento34165638) November 2, 2019
#はじめに
環境は以下の通り
・ionic4
・Nativeとの橋渡しにはcapacitorを使用
・カレンダーについてはionic2-calendarというライブラリーを使用
前回はdayページからイベントを追加、更新、削除ができるようになった。
前回の記事はこちら
・フォームの入力チェックについては以下を参考に
ionic フォームで変なこと書いてないか、バリデーションを実装
・ログイン時のFirebase側でのエラーを表示する方法については以下を参考に
Firebase ionic サインアップのエラータイプを判別して、アラート表示を変える
#概要
このアプリは各アカウントがイベントを持つ事で、ようやく自分以外の人でも使えるようになる。そうでないと全ユーザーが共通のカレンダーを見る事になるからね・・・
Ionic4 + Firebaseでのユーザー認証はこちらで詳しく解説されている。
私自身もこの記事を参考に作った。
今回はとりあえずユーザーを作成して、そのユーザーでログインできるようになる所までやってみる。
ログインしているユーザーごとに、表示するカレンダーを変えるのは次回という事で。
#FirebaseコンソールでAuthenticationを設定
メールアドレスとパスワードでの認証を有効にする。

#必要なモジュールをapp.module.tsで登録
AngularFireAuthModule
をimportして@NgModule
に追加
import { NgModule, LOCALE_ID } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '@angular/common/http';
//カレンダー関係
import { NgCalendarModule } from 'ionic2-calendar';
import { registerLocaleData } from '@angular/common';
import localeJa from '@angular/common/locales/ja';
//Firebaseを利用するためのモジュール
import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { AngularFireAuthModule } from '@angular/fire/auth';
//Firebase設定情報ファイルをインポート
import { environment } from '../environments/environment';
registerLocaleData(localeJa);
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
NgCalendarModule,
HttpClientModule,
AngularFireModule.initializeApp(environment.firebase),
AngularFirestoreModule,
AngularFireAuthModule
],
providers: [
StatusBar,
SplashScreen,
NativeStorage,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
{ provide: LOCALE_ID, useValue: 'ja-JP' }
],
bootstrap: [AppComponent]
})
export class AppModule {}
#authページを作る
本当は素直にloginページ
と呼びたい所だが、認証関係の処理をhomeページ
でもやったようにserviceにまとめたい。
ログイン以外にも認証(Authentication)したい時は出てくるので、ここではauthページ
として作成したいと思う。
ionic g page auth
##auth.service.ts
ionic g service auth/auth
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AngularFireAuth } from '@angular/fire/auth';
import { Observable } from 'rxjs';
import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';
import { switchMap } from 'rxjs/operators';
import { LoadingController, AlertController, ToastController } from '@ionic/angular';
export interface User {
uid: string;
email: string;
displayName?: string;
myCustomData?: string;
photoURL?: string;
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
user$: Observable<User[]>;
constructor(
private router: Router,
private afs: AngularFirestore,
private afAuth: AngularFireAuth,
public loadingCtrl: LoadingController,
private alertCtrl: AlertController,
private toastCtrl: ToastController
) {
this.user$ = this.afAuth.authState.pipe(
switchMap(user => {
if (user) {
this.updateUserData(user);
return this.afs.doc<User[]>(`users/${user.uid}`).valueChanges();
} else {
this.router.navigateByUrl('/auth');
}
})
);
}
async login(email: string, password: string) {
await this.afAuth.auth
.signInWithEmailAndPassword(email, password)
.then(async user => {
this.router.navigateByUrl('/home');
const toast = await this.toastCtrl.create({
message: `${this.afAuth.auth.currentUser.displayName}さん、こんにちは!`,
duration: 3000
});
await toast.present();
})
.catch(error => {
console.log(error);
const code = error.code;
const header = 'ログイン失敗';
let message = 'エラーが発生しました。もう一度お試しください。。。';
if (code === 'auth/user-not-found') {
message =
'ユーザーが見つかりません。メールアドレスを確認して下さい。。。';
} else if (code === 'auth/wrong-password') {
message =
'そのメールアドレスは存在していますが、パスワードが違います。。。';
}
this.showAlert(header, message);
this.router.navigateByUrl('/auth');
});
}
private showAlert(header: string, message: string) {
this.alertCtrl
.create({
header: header,
message: message,
buttons: ['OK']
})
.then(alertEl => alertEl.present());
}
async logout() {
this.afAuth.auth.signOut();
}
async presentLoading(loading) {
return await loading.present();
}
updateUserData(user) {
// ログイン時にユーザーデータをfirestoreに設定
// usersコレクションの下に入る
const userRef: AngularFirestoreDocument<User> = this.afs.doc(
`users/${user.uid}`
);
const data = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL
};
return userRef.set(data, { merge: true });
}
getUser(): Observable<User[]> {
return this.user$;
}
}
##auth.page.html
実質上のログインページ
<ion-header>
<ion-toolbar color="primary">
<ion-title>ログイン</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-grid>
<ion-row>
<ion-col size="12" size-sm="8" offset-sm="2">
<ion-item class="loginHeader" lines="none" text-center>
</ion-item>
<form #f="ngForm" class="loginForm">
<ion-item class="formItem">
<ion-label position="floating">
<ion-icon name="mail"></ion-icon>
メールアドレス
</ion-label>
<ion-input required email [(ngModel)]="login.email" type="email" name="login.email" #emailCtrl="ngModel">
</ion-input>
</ion-item>
<ion-item *ngIf="!emailCtrl.valid && emailCtrl.touched" lines="none">
<ion-label color="danger">メールアドレスを入力してね!</ion-label>
</ion-item>
<ion-item class="formItem">
<ion-label position="floating">
<ion-icon name="key" padding-right></ion-icon>パスワード
</ion-label>
<ion-input required [(ngModel)]="login.password" type="password" name="login.password" #passwordCtrl="ngModel"
minlength="6">
</ion-input>
</ion-item>
<ion-item *ngIf="!passwordCtrl.valid && passwordCtrl.touched" lines="none">
<ion-label color="danger">パスワードは6文字以上</ion-label>
</ion-item>
<ion-button class="submitBtn" [disabled]="!f.form.valid" (click)="userLogin()" seize="large" expand="block">
ログイン
</ion-button>
</form>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="12" size-sm="8" offset-sm="2">
<ion-button class="subLink" (click)="gotoSignup()" fill="clear">
アカウント登録はこちらから
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
##auth.page.ts
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { ToastController } from '@ionic/angular';
import { AngularFireAuth } from '@angular/fire/auth';
import { LoadingController } from '@ionic/angular';
@Component({
selector: 'app-auth',
templateUrl: './auth.page.html',
styleUrls: ['./auth.page.scss']
})
export class AuthPage implements OnInit {
login: {
email: string;
password: string;
} = {
email: '',
password: ''
};
constructor(
private router: Router,
private authService: AuthService,
private toastCtrl: ToastController,
private afAuth: AngularFireAuth,
private loadingCtrl: LoadingController,
) {}
ngOnInit() {}
async userLogin() {
const loading = await this.loadingCtrl.create({
message: 'ログイン中です'
});
this.presentLoading(loading);
this.authService.login(this.login.email, this.login.password)
.then(() => {
loading.dismiss();
});
}
gotoSignup() {
this.router.navigateByUrl('/signup');
}
async presentLoading(loading) {
return await loading.present();
}
}
#signupページを作る
ionic g page signup
##signup.page.html
<ion-header>
<ion-toolbar color="primary">
<ion-title>アカウント登録</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-grid>
<ion-row>
<ion-col size="12" size-sm="8" offset-sm="2">
<form #f="ngForm" class="signupForm">
<ion-item class="formItem">
<ion-label position="floating">
<ion-icon name="mail"></ion-icon>メールアドレス
</ion-label>
<ion-input required email [(ngModel)]="signup.email" type="email" name="signup.email" #emailCtrl="ngModel">
</ion-input>
</ion-item>
<ion-item *ngIf="!emailCtrl.valid && emailCtrl.touched" lines="none">
<ion-label color="danger">メールアドレスを入力してね!</ion-label>
</ion-item>
<ion-item class="formItem">
<ion-label position="floating">
<ion-icon name="key"></ion-icon>パスワード
</ion-label>
<ion-input required [(ngModel)]="signup.password" type="password" name="signup.password" minlength="6"
#passwordCtrl="ngModel">
</ion-input>
</ion-item>
<ion-item *ngIf="!passwordCtrl.valid && passwordCtrl.touched" lines="none">
<ion-label color="danger">パスワードは6文字以上</ion-label>
</ion-item>
<ion-item class="formItem">
<ion-label position="floating">
<ion-icon name="person"></ion-icon>名前
</ion-label>
<ion-input required [(ngModel)]="signup.name" type="text" name="signup.name"></ion-input>
</ion-item>
<ion-button class="submitBtn" [disabled]="!f.form.valid" (click)="signUp()" size="large" expand="block">
アカウント登録
</ion-button>
</form>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="12" size-sm="8" offset-sm="2">
<p>アカウント登録を完了すると、
<a href="">プライバシーポリシー</a>
に同意した事になります。</p>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="12" size-sm="8" offset-sm="2">
<ion-button class="subLink" (click)="goLogin()" fill="clear">
ログインはこちら
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
##signup.page.ts
import { Router } from '@angular/router';
import { AuthService } from './../auth/auth.service';
import { Component, OnInit } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import { ToastController, AlertController } from '@ionic/angular';
@Component({
selector: 'app-signup',
templateUrl: './signup.page.html',
styleUrls: ['./signup.page.scss']
})
export class SignupPage implements OnInit {
signup: {
email: string;
password: string;
name: string;
} = {
email: '',
password: '',
name: ''
};
constructor(
private router: Router,
private afAuth: AngularFireAuth,
private toastCtrl: ToastController,
public authService: AuthService,
private alertCtrl: AlertController
) { }
ngOnInit() { }
signUp() {
this.afAuth.auth
.createUserWithEmailAndPassword(this.signup.email, this.signup.password)
.then(created => {
const newUser = created.user;
newUser.updateProfile({
displayName: this.signup.name,
photoURL: ''
})
.then(async () => {
console.log('signup ' + this.signup.name);
const toast = await this.toastCtrl.create({
message: `${created.user.displayName}さんを登録しました`,
duration: 3000
});
this.authService.updateUserData(created.user);
this.router.navigateByUrl('/home');
await toast.present();
});
})
.catch(error => {
console.log(error);
const code = error.code;
let message = 'エラーが発生しました。もう一度お試しください。。。';
if (code === 'auth/email-already-in-use') {
message = 'このメールアドレスは既に登録されています。。。';
}
this.showAlert(message);
});
}
goLogin() {
this.router.navigateByUrl('/auth');
}
private showAlert(message: string) {
this.alertCtrl
.create({
header: 'ユーザー登録に失敗',
message: message,
buttons: ['OK']
})
.then(alertEl => alertEl.present());
}
}
urlの最後を/signup
にしてアクセスしてみましょう。
アカウント登録とログインがうまくいってるなら、Firebaseコンソールでもユーザーが登録されていることが確認できる。
#最後に
続き
その6 ログアウト処理とサイドバー
ログアウトがまだ一切できていないので、次回やりたい。
各ストアで配信されてます。
興味があれば使ってみてください。