In this post I will describe how to use Firebase email/password functionality in an Ionic 3 application using AngularFire2.
We will create a login form, a signup form and a password recovery form that will bind to your Firebase application.
This post is a based on my previous post about the setup of Ionic 3, Angularfire2 and Firebase so I will go on from there.
The sourcecode for the final application is on GitHub at https://github.com/henkie14/ionic3Firebase
Now let’s start with the login form…
I’m using a model drive form (or reactive form), for more information about it and the differences to template driven forms(AngularJS) you can read this post:
http://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/
I’m not going to explain how this form works, but will focus on the login functionality using Firebase authentication.
So change your home.html file to the code below.
<ion-header> <ion-navbar> <ion-title> Ionic3Firebase </ion-title> </ion-navbar> </ion-header> <ion-content padding> <form [formGroup]="loginForm" novalidate> <ion-row> <ion-item> <ion-label for="email"></ion-label> <ion-input type="email" value="" placeholder="Email" formControlName="email"></ion-input> </ion-item> </ion-row> <ion-row> <ion-item> <ion-label for="password"></ion-label> <ion-input type="password" placeholder="Password" formControlName="password"></ion-input> </ion-item> </ion-row> <ion-row> <button (click)="login()" ion-button block>Log in</button> </ion-row> </form> </ion-content>
And change your home.ts like this:
import { Component } from '@angular/core'; import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms'; import { NavController } from 'ionic-angular'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { loginForm: FormGroup; email: AbstractControl; password: AbstractControl; error: any; constructor(public navCtrl: NavController, private fb: FormBuilder) { this.loginForm = this.fb.group({ 'email': ['', Validators.compose([Validators.required, Validators.pattern(/[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)])], 'password': ['', Validators.compose([Validators.required, Validators.minLength(1)])] }); this.email = this.loginForm.controls['email']; this.password = this.loginForm.controls['password']; } login(): void { if(this.loginForm.valid) { console.log(this.email.value, this.password.value); alert('Implement authentication'); } } }
Now you should have a working login form, that will show an alert and logs the email and password to the console.
Please try it:
$ionic serve
Next will be the authentication provider…
So let’s create a provider by using the ionic generate command.
$ionic g provider AuthProvider
This will create a new file ..\src\providers\auth-provider.ts
You should update this file to something like this:
import { Injectable } from '@angular/core'; import { AngularFireAuth } from 'angularfire2/auth'; import { Observable } from "rxjs/Observable"; @Injectable() export class AuthProvider { constructor(private af: AngularFireAuth) { } loginWithEmail(credentials) { return Observable.create(observer => { this.af.auth.signInWithEmailAndPassword(credentials.email, credentials.password ).then((authData) => { //console.log(authData); observer.next(authData); }).catch((error) => { observer.error(error); }); }); } }
Next we need to make the Module aware of this service, so we will have to change the app.module.ts file again.
import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; import { SplashScreen } from '@ionic-native/splash-screen'; import { StatusBar } from '@ionic-native/status-bar'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; import { AngularFireModule } from 'angularfire2'; import { AuthProvider} from '../providers/auth-provider' //Added AuthProvider export const firebaseConfig = { apiKey: '******', authDomain: '******', databaseURL: '******', storageBucket: '******', messagingSenderId: '******' }; @NgModule({ declarations: [ MyApp, HomePage ], imports: [ BrowserModule, IonicModule.forRoot(MyApp), AngularFireModule.initializeApp(firebaseConfig) ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage ], providers: [ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}, AuthProvider ] }) export class AppModule {}
Now we can implement it in out home page, so update home.ts as below
import { Component } from '@angular/core'; import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms'; import { NavController } from 'ionic-angular'; import { AuthProvider } from '../../providers/auth-provider'; //added AuthProvider @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { loginForm: FormGroup; email: AbstractControl; password: AbstractControl; error: any; constructor(public navCtrl: NavController, private fb: FormBuilder, public auth: AuthProvider) { //Added AuthProvider this.loginForm = this.fb.group({ 'email': ['', Validators.compose([Validators.required, Validators.pattern(/[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)])], 'password': ['', Validators.compose([Validators.required, Validators.minLength(1)])] }); this.email = this.loginForm.controls['email']; this.password = this.loginForm.controls['password']; } login(): void { if(this.loginForm.valid) { //console.log(this.email.value, this.password.value); //alert('Implement authentication'); var credentials = ({email: this.email.value, password: this.password.value}); //Added next lines this.auth.loginWithEmail(credentials).subscribe(data => { console.log(data); }); } } }
Let’s start our application again:
$ionic serve
And try to login by entering a valid email and a password.
If everything is ok, you should see an errorpage saying something like this:
Uncaught (in promise): Error: The given sign-in provider is disabled for this Firebase project. Enable it in the Firebase console, under the sign-in method tab of the Auth section.
You can fix this by enabling the email/password provider in your Firebase project.
So open up your Firebase console at https://console.firebase.google.com and select your Firebase project.
Select authentication on the left hand side and click the ‘set up sign-in method’ button.
Next enable the Email/Password provider (We will enable Facebook and Google in a later stage).
So now we can try again…
But it’s still not working. You should see an error page containing the following error:
Uncaught (in promise): Error: There is no user record corresponding to this identifier. The user may have been deleted.
As you can see, this isn’t a technical issue, but Firebase returns an error message, because the user isn’t available. We should actually handle this error in our code, so we can show the user a nice message like ‘This combination of email/password was not found.’ and ask them if they want to sign up for this app.
For testing purposes you could create an account in the Firebase console, but we actually need a sign up page…
So let’s generate a signup page…
$ionic g page Signup
And copy the following html in the generated ..\pages\signup\signup.html page
<ion-header> <ion-navbar> <ion-title>Ionic3Firebase</ion-title> </ion-navbar> </ion-header> <ion-content padding> <ion-title>Signup</ion-title> <form [formGroup]="signupForm" (ngSubmit)="submit()" novalidate> <ion-row> <ion-item> <ion-label for="email"></ion-label> <ion-input type="email" value="" placeholder="Email" formControlName="email"></ion-input> </ion-item> </ion-row> <ion-row> <ion-item> <ion-label for="password"></ion-label> <ion-input type="password" placeholder="Password" formControlName="password"></ion-input> </ion-item> </ion-row> <ion-row> <button ion-button type="submit" block>Sign up</button> </ion-row> </form> </ion-content>
And here’s the typescript code to copy in the generated signup.ts file
import { Component } from '@angular/core'; import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms'; import { NavController, NavParams } from 'ionic-angular'; import { AuthProvider } from '../../providers/auth-provider'; import { HomePage } from '../home/home'; @Component({ selector: 'page-signup', templateUrl: 'signup.html' }) export class SignupPage { signupForm: FormGroup; email: AbstractControl; password: AbstractControl; error: any; constructor(public navCtrl: NavController, public navParams: NavParams, private fb: FormBuilder, private auth: AuthProvider) { this.signupForm = this.fb.group({ 'email': ['', Validators.compose([Validators.required, Validators.pattern(/[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)])], 'password': ['', Validators.compose([Validators.required, Validators.minLength(1)])] }); this.email = this.signupForm.controls['email']; this.password = this.signupForm.controls['password']; } submit(): void { if(this.signupForm.valid) { var credentials = ({email: this.email.value, password: this.password.value}); this.auth.registerUser(credentials).subscribe(registerData => { console.log(registerData); alert('User is registered and logged in.'); this.navCtrl.setRoot(HomePage); }, registerError => { console.log(registerError); if (registerError.code === 'auth/weak-password' || registerError.code === 'auth/email-already-in-use') { alert(registerError.message); } this.error = registerError; }); } } }
Some explanation:
The signup page has is almost similar to the home (login) page, but now it makes a call to the registerUser method on the authentication provider. We will create this in the next step.
I’ve added some extra error handling, so it will show an alert when the user already exists or the password is not strong enough. There are much nicer ways to do this, but that’s up to you.
So now the register user functionality. Below is the updated auth-provider.ts
import { Injectable } from '@angular/core'; import { AngularFireAuth } from 'angularfire2/auth'; import { Observable } from "rxjs/Observable"; @Injectable() export class AuthProvider { constructor(private af: AngularFireAuth) { } loginWithEmail(credentials) { return Observable.create(observer => { this.af.auth.signInWithEmailAndPassword(credentials.email, credentials.password ).then((authData) => { //console.log(authData); observer.next(authData); }).catch((error) => { observer.error(error); }); }); } registerUser(credentials: any) { return Observable.create(observer => { this.af.auth.createUserWithEmailAndPassword(credentials.email, credentials.password).then(authData => { //this.af.auth.currentUser.updateProfile({displayName: credentials.displayName, photoURL: credentials.photoUrl}); //set name and photo observer.next(authData); }).catch(error => { //console.log(error); observer.error(error); }); }); } logout() { this.af.auth.signOut(); } get currentUser():string{ return this.af.auth.currentUser?this.af.auth.currentUser.email:null; } }
So besides the register user method, there’s also a new method to logout and a method to get the current user (or actually it’s email address).
If you want to add a displayname (or photo) to your user profile, you could uncomment line 35 and add it to the credentials object passed in to the registerUser object.
Now we have a signup page, you should be able to use it :).
First it has to be imported into the app module, so let’s update the app.module.ts file.
import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; import { SplashScreen } from '@ionic-native/splash-screen'; import { StatusBar } from '@ionic-native/status-bar'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; import { SignupPage } from '../pages/signup/signup'; import { AngularFireModule } from 'angularfire2'; import { AngularFireAuthModule } from 'angularfire2/auth'; import { AuthProvider } from '../providers/auth-provider'; export const firebaseConfig = { apiKey: '*****', authDomain: '*****', databaseURL: '*****', storageBucket: '*****', messagingSenderId: '*****' }; @NgModule({ declarations: [ MyApp, HomePage, SignupPage ], imports: [ IonicModule.forRoot(MyApp), BrowserModule, AngularFireModule.initializeApp(firebaseConfig), AngularFireAuthModule ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage, SignupPage ], providers: [ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}, AuthProvider ] }) export class AppModule {}
Next we have to update the home page.
We will create a new button to navigate to the signup page and we will also create a logout button and show the email address of the logged in user.
So here’s the updated home.html file.
<ion-header> <ion-navbar> <ion-title> Ionic3Firebase </ion-title> </ion-navbar> </ion-header> <ion-content padding> <ion-title>Login</ion-title> <ion-row> <ion-item> <div *ngIf="auth.currentUser">current user is {{auth.currentUser}}</div> </ion-item> </ion-row> <form [formGroup]="loginForm" novalidate> <ion-row> <ion-item> <ion-label for="email"></ion-label> <ion-input type="email" value="" placeholder="Email" formControlName="email"></ion-input> </ion-item> </ion-row> <ion-row> <ion-item> <ion-label for="password"></ion-label> <ion-input type="password" placeholder="Password" formControlName="password"></ion-input> </ion-item> </ion-row> </form> <ion-row> <button (click)="login()" ion-button block>Log in</button> </ion-row> <ion-row> <button [navPush]="signupPage" ion-button block>Sign up</button> </ion-row> <ion-row> <button (click)="logout()" ion-button block>Log out</button> </ion-row> </ion-content>
And here’s the updated home.ts file.
import { Component } from '@angular/core'; import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms'; import { NavController } from 'ionic-angular'; import { AuthProvider } from '../../providers/auth-provider'; import { SignupPage } from '../signup/signup' //Added sign up page @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { loginForm: FormGroup; email: AbstractControl; password: AbstractControl; error: any; signupPage = SignupPage; //Added sing up page constructor(private navCtrl: NavController, private fb: FormBuilder, private auth: AuthProvider) { this.loginForm = this.fb.group({ 'email': ['', Validators.compose([Validators.required, Validators.pattern(/[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)])], 'password': ['', Validators.compose([Validators.required, Validators.minLength(1)])] }); this.email = this.loginForm.controls['email']; this.password = this.loginForm.controls['password']; } login(): void { if(this.loginForm.valid) { var credentials = ({email: this.email.value, password: this.password.value}); this.auth.loginWithEmail(credentials).subscribe(data => { console.log(data); }, error=>{ //Added next lines for handling unknown users console.log(error); if (error.code == 'auth/user-not-found') { alert('User not found'); } }); } } logout(): void { this.auth.logout(); } }
The last past of this post will create a password recovery page.
$ionic g page ResetPassword
Copy the following html in the generated ..\pages\reset-password\reset-password.html page.
<ion-header> <ion-navbar> <ion-title>Ionic2Firebase</ion-title> </ion-navbar> </ion-header> <ion-content padding> <ion-title>ResetPassword</ion-title> <form [formGroup]="resetPasswordForm" (ngSubmit)="submit()" novalidate> <ion-row> <ion-item> <ion-label for="email"></ion-label> <ion-input type="email" value="" placeholder="Email" formControlName="email"></ion-input> </ion-item> </ion-row> <ion-row> <button ion-button type="submit" block>Reset password</button> </ion-row> </form> </ion-content>
And update the reset-password.ts file like this:
import { Component } from '@angular/core'; import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms'; import { NavController, NavParams } from 'ionic-angular'; import { AuthProvider } from '../../providers/auth-provider'; import { HomePage } from '../home/home'; @Component({ selector: 'page-reset-password', templateUrl: 'reset-password.html' }) export class ResetPasswordPage { resetPasswordForm: FormGroup; email: AbstractControl; constructor(public navCtrl: NavController, public navParams: NavParams, private fb: FormBuilder, private auth: AuthProvider) { this.resetPasswordForm = this.fb.group({ 'email': ['', Validators.compose([Validators.required, Validators.pattern(/[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)])] }); this.email = this.resetPasswordForm.controls['email']; } submit(): void { if(this.resetPasswordForm.valid) { this.auth.resetPassword(this.email.value).subscribe(registerData => { alert('Password recovery link is sent.'); this.navCtrl.setRoot(HomePage); }, registerError => { console.log(registerError); if (registerError.code === 'auth/user-not-found') { alert(registerError.message); } }); } } }
This is all similar to the signup page, only thing we have to do is create the resetPassword method on the authentication provider.
Since there is no reset password functionality in AngularFire2 at this moment, we need a workaround.
This can be done by creating a reference to the Firebase SDK itself.
So the auth-provider.ts will look something like this:
import { Injectable } from '@angular/core'; import { AngularFireAuth } from 'angularfire2/auth'; import { Observable } from "rxjs/Observable"; @Injectable() export class AuthProvider { constructor(private af: AngularFireAuth) { } loginWithEmail(credentials) { return Observable.create(observer => { this.af.auth.signInWithEmailAndPassword(credentials.email, credentials.password ).then((authData) => { //console.log(authData); observer.next(authData); }).catch((error) => { observer.error(error); }); }); } registerUser(credentials: any) { return Observable.create(observer => { this.af.auth.createUserWithEmailAndPassword(credentials.email, credentials.password).then(authData => { //this.af.auth.currentUser.updateProfile({displayName: credentials.displayName, photoURL: credentials.photoUrl}); //set name and photo observer.next(authData); }).catch(error => { //console.log(error); observer.error(error); }); }); } resetPassword(emailAddress:string){ return Observable.create(observer => { this.af.auth.sendPasswordResetEmail(emailAddress).then(function(success) { //console.log('email sent', success); observer.next(success); }, function(error) { //console.log('error sending email',error); observer.error(error); }); }); } logout() { this.af.auth.signOut(); } get currentUser():string{ return this.af.auth.currentUser?this.af.auth.currentUser.email:null; } }
Next we need to import the reset password page in the app module and create a new button on the homepage to navigate to the reset password page.
Here’s the updated app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; import { SplashScreen } from '@ionic-native/splash-screen'; import { StatusBar } from '@ionic-native/status-bar'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; import { SignupPage } from '../pages/signup/signup'; import { ResetPasswordPage } from '../pages/reset-password/reset-password'; //Added reset password page import { AngularFireModule } from 'angularfire2'; import { AngularFireAuthModule } from 'angularfire2/auth'; import { AuthProvider } from '../providers/auth-provider'; export const firebaseConfig = { apiKey: '*****', authDomain: '*****', databaseURL: '*****', storageBucket: '*****', messagingSenderId: '*****' }; @NgModule({ declarations: [ MyApp, HomePage, SignupPage, ResetPasswordPage //Added reset password page ], imports: [ IonicModule.forRoot(MyApp), BrowserModule, AngularFireModule.initializeApp(firebaseConfig), AngularFireAuthModule ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage, SignupPage, ResetPasswordPage //Added reset password page ], providers: [ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}, AuthProvider ] }) export class AppModule {}
And here’s the updated home.html
<ion-header> <ion-navbar> <ion-title> Ionic3Firebase </ion-title> </ion-navbar> </ion-header> <ion-content padding> <ion-title>Login</ion-title> <ion-row> <ion-item> <div *ngIf="auth.currentUser">current user is {{auth.currentUser}}</div> </ion-item> </ion-row> <form [formGroup]="loginForm" novalidate> <ion-row> <ion-item> <ion-label for="email"></ion-label> <ion-input type="email" value="" placeholder="Email" formControlName="email"></ion-input> </ion-item> </ion-row> <ion-row> <ion-item> <ion-label for="password"></ion-label> <ion-input type="password" placeholder="Password" formControlName="password"></ion-input> </ion-item> </ion-row> </form> <ion-row> <button (click)="login()" ion-button block>Log in</button> </ion-row> <ion-row> <button [navPush]="signupPage" ion-button block>Sign up</button> </ion-row> <!--new link to sign up page--> <ion-row> <button [navPush]="resetPasswordPage" ion-button block>Reset password</button> </ion-row> <ion-row> <button (click)="logout()" ion-button block>Log out</button> </ion-row> </ion-content>
And the updated home.ts
import { Component } from '@angular/core'; import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms'; import { NavController } from 'ionic-angular'; import { AuthProvider } from '../../providers/auth-provider'; import { SignupPage } from '../signup/signup' import { ResetPasswordPage } from '../reset-password/reset-password' //Added reset password page @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { loginForm: FormGroup; email: AbstractControl; password: AbstractControl; error: any; signupPage = SignupPage; resetPasswordPage = ResetPasswordPage //Added reset password page constructor(private navCtrl: NavController, private fb: FormBuilder, private auth: AuthProvider) { this.loginForm = this.fb.group({ 'email': ['', Validators.compose([Validators.required, Validators.pattern(/[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)])], 'password': ['', Validators.compose([Validators.required, Validators.minLength(1)])] }); this.email = this.loginForm.controls['email']; this.password = this.loginForm.controls['password']; } login(): void { if(this.loginForm.valid) { var credentials = ({email: this.email.value, password: this.password.value}); this.auth.loginWithEmail(credentials).subscribe(data => { console.log(data); }, error=>{ console.log(error); if (error.code == 'auth/user-not-found') { alert('User not found'); } }); } } logout(): void { this.auth.logout(); } }
So now let’s start our application again:
$ionic serve
You should now be able to send and receive a reset password email from the Firebase application.
If you like to change the email template and properties, you can go to the Firebase console, select your application, select authentication on the left hand side and select the ‘Email Templates’ tab.
Enjoy it.
8 comments. Leave new
Great Job.
I will try it later today, but looks perfect to me.
Many Thanks
Very good your post!
Do you intend to continue this project with Firebase, using Database and Storage?
I would like to make a list, and show the details of each item with Ionic, everything consumed from Firebase, to the images that will be in the list, and not the images of each detail of each item.
Congratulations on the post, because you explain everything correct!
That’s still my intention, but I’m very busy at the moment.
Great job!! <3
I keep getting “Runtime Error
co.submit is not a function”
on the homepage. Any ideas?
BTW, this was towards the beginning of your tutorial, before we generate the Signup page. I see we have a submit function for other pages – should it also be on this page?
there is a mistake in the home page, you can replace the
Log in
with
Log in
and change
login(): void
to
submit(): void
in home.ts
I’ve removed (ngSubmit)=”submit()” should be working now.