Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Angular Firebase Authentication Tutorial - Email Password Signup

Episode 7 written by Jeff Delaney
full courses and content on fireship.io

Health Check: This lesson was last reviewed on and tested with these packages:

  • Angular v4
  • AngularFire2 v4

Update Notes: Breaking changes have been introduced since this video's original release. Find updated versions about Firebase User Auth.

Find an issue? Let's fix it

Email/Password authentication in Angular requires a few extra steps compared to OAuth or Anonymous Auth. In this lesson, we are using the AngularFire2 package to login and signup users with an email and password, as well provide reactive form validation. I highly recommend checking our full AngularFirebase Authentication series

Angular Firebase Authentication Series

Let’s start by generating the necessary components.


ng g service core/auth
ng g component users/login-form


## Step 1: Enable Email/Password Auth in Firebase

Make sure to enable email/password auth in Firebase first.


Make sure to activate the email password provider in Firebase.

Make sure to activate the email password provider in Firebase.

## Step 2: Updating the Auth Service

I will be building upon the AuthService from our previous OAuth tutorial. You can find the full auth service code on github.

Start by defining the authState in the constructor.

@Injectable()
export class AuthService {

authState: FirebaseAuthState = null;

constructor(private af: AngularFire,
private db: AngularFireDatabase,
private router:Router) {

af.auth.subscribe((auth) => {
this.authState = auth;
});
}
}

First, we need to define a new class for EmailPasswordCredentials that has members for the email and password fields. Using this class will give you explicit control over the login process. In this case, we only need email and password strings. You might also extract this to its own class file, but I adding it directly to service for now.

auth.service.ts

export class EmailPasswordCredentials {
email: string;
password: string;
}

You can see we use two separate functions for signup and login. When triggering the login function from AngularFire2 it is necessary to pass the provider and methods as the second argument.

emailSignUp(credentials: EmailPasswordCredentials): firebase.Promise<FirebaseAuthState> {
return this.af.auth.createUser(credentials)
.then(() => console.log("success"))
.catch(error => console.log(error));
}

emailLogin(credentials: EmailPasswordCredentials): firebase.Promise<FirebaseAuthState> {
return this.af.auth.login(credentials,
{ provider: AuthProviders.Password,
method: AuthMethods.Password
})
.then(() => console.log("success"))
.catch(error => console.log(error));
}

Step 3: Building the Reactive Login/Signup Form

Our goal is to create a form validation that can scale up in complexity as needed. At this point, we only need to validate the email and password, but your app might need to collect other complex information from users, such as a username, address, phone number, etc. The reactive form model used below is the best option for creating large forms with custom validators.

Angular Reactive Form Concepts

  • FormBuilder An angular class for writing custom forms with less verbose code.
  • FormGroup The parent that holds the form controls. We can pass the form group values to the login or signup functions when the form is valid.
  • FormControl Controls are the individual inputs in the form, in this case email and password inputs.

How our Reactive Form Works

We call a function named buildForm during the NgOnInit lifecycle hook. This initializes the FormGroup, the FormControls, and their validation rules. It also triggers a function named onValueChange, which will update our validation messages whenever the user changes the forms value. The nice thing about this approach is that it allows us to define the form and its validation has just a plain JavaScript object. The signup and login functions will triggered when the form is submitted.

It’s also worth noting that the Validators are related to HTML5 constraint validators. The patten validator uses regex to force a certain pattern, which is a password with at least one letter and one number in this example. You can create your own custom validators, but that’s a whole lesson on its own.

users/user-form.component.ts

This may seem like a lot of code, but it’s doing a lot. This component handles both signup, login, form validation, and password resetting.

import { Component, OnInit } from '@angular/core';
import { AuthService } from "../../core/auth.service";
import { ReactiveFormsModule, FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({
selector: 'user-form',
templateUrl: './user-form.component.html',
styleUrls: ['./user-form.component.scss']
})
export class UserFormComponent implements OnInit {

userForm: FormGroup;
newUser: boolean = true; // to toggle login or signup form
passReset: boolean = false;

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

ngOnInit(): void {
this.buildForm();
}

toggleForm(): void {
this.newUser = !this.newUser;
}

signup(): void {
this.auth.emailSignUp(this.userForm.value)
}

login(): void {
this.auth.emailLogin(this.userForm.value)
}

resetPassword() {
this.auth.resetPassword(this.userForm.value['email'])
.then(() => this.passReset = true)
}

buildForm(): void {
this.userForm = this.fb.group({
'email': ['', [
Validators.required,
Validators.email
]
],
'password': ['', [
Validators.pattern('^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$'),
Validators.minLength(6),
Validators.maxLength(25)
]
],
});

this.userForm.valueChanges.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); // reset validation messages
}

// Updates validation state on form changes.
onValueChanged(data?: any) {
if (!this.userForm) { return; }
const form = this.userForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}

formErrors = {
'email': '',
'password': ''
};

validationMessages = {
'email': {
'required': 'Email is required.',
'email': 'Email must be a valid email'
},
'password': {
'required': 'Password is required.',
'pattern': 'Password must be include at one letter and one number.',
'minlength': 'Password must be at least 4 characters long.',
'maxlength': 'Password cannot be more than 40 characters long.',
}
};

}

In the template, we bind the userForm object the HTML form. Now we can pass the form controls using the formControlName attribute on input elements. They will automatically use the validation logic defined in the TypeScript. The errors messages can then be displayed anywhere in the template with a single piece code. Here’s what the full form will look like for the new user signup.

users/user-form.component.html

<form [formGroup]="userForm"  *ngIf="newUser"  (ngSubmit)="signup()">
<h3>New User Signup</h3>
<p (click)="toggleForm()">Already Registered?</p>
<hr>

<label for="email">Email</label>
<input type="email"
formControlName="email" required >

<div *ngIf="formErrors.email">
{{ formErrors.email }}
</div>

<label for="password">Password</label>
<input type="password"
formControlName="password" required >

<div *ngIf="formErrors.password">
{{ formErrors.password }}
</div>

<button type="submit" [disabled]="!userForm.valid">Submit</button>

<span *ngIf="userForm.valid" >Form is valid</span>

</form>

Toggling between Login and Signup

Depending on the complexity of your signup process, you may want separate components and forms for Login and Signup. In this simple example, we are reusing the same form for both actions. We just use a boolean variable to toggle between the forms in the template with the *ngIf="newUser" directive.

users/user-form.component.ts

newUser: boolean = true;
toggleForm() {
this.newUser = !this.newUser;
}

Submitting the Form with ngSubmit

Angular needs to override the standard HTML form submit process with its own ngSubmit event. You listen for the ngSubmit event by adding it to the beginning of the form, which will fire one of the signup() or login() functions when the submit button is triggered. You just need to add a button with type=submit inside the form.

users/user-form.component.ts

signup(): void {
this.auth.emailSignUp(this.userForm.value)
}

login(): void {
this.auth.emailLogin(this.userForm.value)
}

users/user-form.component.html

<form [formGroup]="userForm"  *ngIf="newUser"  (ngSubmit)="signup()">
<!-- form elements -->
<button type="submit" [disabled]="!userForm.valid">Submit</button>
</form>

Bonus Step: Resetting the Password

Firebase has an email/password reset process built in, but it’s not integrated into the AngularFire2 package. In this section, we interact directly with the Firebase API to trigger the a password reset. This will email the existing user a link that they can follow to securely update their password.

auth.service.ts

import * as firebase from 'firebase';
//...omitted
resetPassword(email: string) {
var auth = firebase.auth();

return auth.sendPasswordResetEmail(email)
.then(() => console.log("email sent"))
.catch((error) => console.log(error))
}

user-form.component.ts

passReset: boolean = false;
resetPassword() {
this.auth.resetPassword(this.userForm.value['email'])
.then(() => this.passReset = true)
}

user-form.component.html

<p *ngIf="!passReset && userForm.controls.email.valid" (click)="resetPassword()">Reset Password for {{userForm.value.email}}</p>
<p *ngIf="passReset" >Reset requested. Check your email instructions.</p>