Building the frontend Food order system with Angularjs (building angular authentication).Part8

in #utopian-io6 years ago

Contribution Repository

https://github.com/angular/angular

Disclaimer

Before understanding this tutorial, you must have a little knowledge of SASS and Bootstrap 4

In our last tutorial we where able to signup a user for our application.
In this series, we are going to work on the login feature of the application and also learn how to the local storage to save data that needs to be accessed he in the application.

Project structure.

  • node_modules/
  • src/
    • app
      • app-navbar/
      • item/
      • items-feed/
      • signup/
      • login/
      • app-routing.module
      • app.component.html
      • app.component.sass
      • app.component.ts
      • app.module.ts
      • auth.service.ts
      • item.service
      • cache.service
    • asset/
    • environments/
    • item.ts
    • user.ts
    • auth.ts
  • .angular-cli.json
  • .editorconfig
    +.....

Rating

This tutorial is rated Intermediate.

Requirement.

What would i learn? / Table of content.

  1. Recap of the previous tutorial.
  2. Building the login component.
  3. Creating the class interface for authentication.
  4. Building the login markup and styles.
  5. Adding a login method to the Auth service.
  6. Building the logout method.
  7. Building a cache service.
  8. Logging a user in the application and validation.
  9. Adding the logout method to the navbar component

Recap of the previous tutorial.

We were able to generate a signup component no our application, the data for signing up a user was gotten from the form input using two way binding and the data was sent as a post request to the auth service we created. The response gotten from the server was displayed using a message variable by interpolation on the signup.component.html

authentication.jpg

Schematic diagram of relationships for the present tutorial

Building the login component.

Now that we are done with signing up a user for our application, lets login the said user into our application.
Login involve a series of steps;

  1. Validation of the user input;
  2. Two way data binding of the input into the component.
  3. Sending a post request with the user data and listening for response.
  4. If response was successful, save the JWT token and the user object using a cache service on the browsers' local storage.

Lets begin by generating our login component

ng generate component login

Creating the class interface for authentication

Next up, we also need to create an interface for our login data , in the src directory, create a file called auth.ts add the code for the type on the interface.

export interface Auth {
    email: string;
    password:string;
}

Both properties are expected to have a type of string

Building the login markup and styles

We are going to be using the bootstrap form template for this feature, open up the login.component.html file in the login folder and add the markup below.

<div class="login__container">
  <div class="container">
    <div>
        <form class="form-signin"  (ngSubmit)="login()" #loginForm="ngForm">  
            <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
            <label for="inputEmail" class="sr-only">Email address</label>
            <input type="email" name="email" id="inputEmail" class="form-control" placeholder="Email address" autofocus  #email="ngModel" required minlength="6" #spy>
            </div>
            <label for="inputPassword" class="sr-only">Password</label>
            <input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
            <div class="checkbox mb-3">
              <label>
                <input type="checkbox" value="remember-me"> Remember me
              </label>
            </div>
            <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
          </form>
    </div>
  </div>
</div>

All we simply did was to create two input with different labels and a check box to remember the user.

lets add the styles to the login.component.sass, this just basic styling, where we make the body 100% and use flex box for responsiveness.

html
    height: 100%;
body 
    height: 100%;
    display: -ms-flexbox;
    display: -webkit-box;
    display: flex;
    -ms-flex-align: center;
    -ms-flex-pack: center;
    -webkit-box-align: center;
    align-items: center;
    -webkit-box-pack: center;
    justify-content: center;
    padding-top: 40px;
    padding-bottom: 40px;
    background-color: #f5f5f5;

.form-signin 
    width: 100%;
    max-width: 330px;
    padding: 15px;
    margin: 0 auto;

.form-signin .checkbox 
    font-weight: 400;

.form-signin .form-control 
    position: relative;
    box-sizing: border-box;
    height: auto;
    padding: 10px;
    font-size: 16px;

.form-signin .form-control:focus 
    z-index: 2;

.form-signin input[type="email"] 
    margin-bottom: -1px;
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;

.form-signin input[type="password"] 
    margin-bottom: 10px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;

.login__container
    margin-top: 80px;

The result on the DOM beow.

Building the login service.

In our last tutorial, we generated and built the auth service for our application. In this series we are going to work on the method responsible for logging in and logging out a user

Open the auth.service.ts file and import the newly created auth interface.

import { Auth } from '../auth';

Lets add some variable for the login data, login endpoint, and a variable LoggedIn which is set to a Boolean false

  loginEndpoint = 'http://localhost:3000/api/users/authenticate';
  loginData: any;
  private loggedIn = false;

Lets create the method to send the post request to the server with the loginData in its request.
We would need to toggle the variable loggedIn to true then send a post request with the loginData to the login endpoint.
Note; data must be JSON stringed before sending to the endpoint.

login (loginData ): Observable<{} | Auth>{
    this.loggedIn = true;
    return this.http.post<Auth>(this.loginEndpoint,JSON.stringify(loginData), this.httpOptions) 
  }

We would have to monitor a stream of data and announce to the application that the data has been mutated. In this case, we need to announce a logged in user to the application.
Add two variables, one as a new Subject and the other as an observable.

private authAnnouncerSource = new Subject<object|null>();
authAnnouncer$ = this.authAnnouncerSource.asObservable()

Right now, we are watching the authAnnouncer which is an observable. lets create a method that announces that there is a new data in the observable.

announceLogin(user) {
    return this.authAnnouncerSource.next(user);
  }

The method next is used to access the next object in the observable

Building the logout method

Logging out is simple a check to know if there is a user and a JWT token in the database.
If there is a Jwt token and a user object, to logout we need to delete the items from local storage, so we need a method to remove the jwt and the user object. Lastly we need to pass the observable a null value to let it know that the variable has changed.

logOut () {
    localStorage.removeItem('jwtToken');
    localStorage.removeItem('user');
    this.announceLogin(null)
  }

Building a cache service

We are going to need a cache service which can check, get and set a specific item in the local storage.

ng g service cache

Open the new created service and add the following methods;

Check local storage
  hasItem(user:string) {
    return typeof localStorage.getItem(user);
  }
Get item from local storage
 getItem (user:string) {
    return localStorage.getItem(user);
  }
Set item in local storage.
 setItem(user:object) {
    return localStorage.setItem('user', JSON.stringify(user));
  }

Here we have the different methods to set, check and get the user from the local storage.

Complete cache.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class CacheService {

  constructor() { }

  hasItem(user:string) {
    return typeof localStorage.getItem(user);
  }

  getItem (user:string) {
    return localStorage.getItem(user);
  }

  setItem(user:object) {
    return localStorage.setItem('user', JSON.stringify(user));
  }

}

Logging a user in the application and validation.

We are going to need three import to this component, we need the auth service to send the post request that includes the login data, the interface to type hint and finally a router to navigate after the authentication is successful.

import { AuthService } from '../auth.service';
import { Router } from "@angular/router";
import {Auth} from '../../auth';

Lets create an object and a variable to hold the authentication data and the message from the server respectively. Finally add another variable data to hold the response data from the server.

data: any;

  loginData: Auth = {
    "email": "",
    "password": ""
  }

  message : String = '';

Note; All the values are set to an empty string since it would be gotten from the form through two way data binding.

Next up instantiate the the Router and the authService in the constructor method.

constructor(private AuthService: AuthService, private router: Router) {}
Login method

The login method uses the login method on the auth service and pass the authData to the method and subscribe for a response from the server.
In a call back function, the res.data.token and res.data.user is save to local storage. Lastly, a new logged in user is announced using the observable in the authService.

  login(): void {
    this.AuthService.login(this.loginData)
        .subscribe((res:any):any => {

          localStorage.setItem('jwtToken', res.data.token);
          localStorage.setItem('user',JSON.stringify(res.data.user));
          this.AuthService.announceLogin(res.data.user)
          this.router.navigate(['items']);
        }, err => {
          this.message = err.error.errors[0].detail;
        });
  }

Now that our login method is ready, let work on the two way data binding to get the data from the input into the login.component.ts.
Open the login.component.html and update the form with the [(ngModel)] and [ngModelOptions] on the input tags

<input type="email" name="email" id="inputEmail" class="form-control" placeholder="Email address" [(ngModel)]="loginData.email" [ngModelOptions]="{standalone: true}"
<input type="password" id="inputPassword" class="form-control" [(ngModel)]="loginData.password" [ngModelOptions]="{standalone: true}" placeholder="Password" required>

The above ngModel is used for two way data binding to bind the input to the components' authData object.

Validation on the email input

Just below the email input we need to display some validation messages,
we only display when the email is invalid, dirty or untouched. To understand more on form validation, i advice you read the official documentation on form validation

<div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger">
      <div *ngIf="email.errors.required">
        Email is required.
      </div>
      <div *ngIf="email.errors.minlength">
        email must be at least 6 characters long.
      </div>
</div>

Displaying flash message from the server.

We add a snippet of code on the login.component.html and use the directive *ngIf to toggle the display of error or success messages.
Note; the message variable is displayed using interpolation

<div *ngIf="message !==''" class="alert alert-danger alert-dismissible" role="alert">
              <button type="button" class="close" data-dismiss="alert"aria-label="Close"><span aria-hidden="true">&times;</span></button>
                {{message}}
 </div>

Lastly we add the handler to submit our form on the click of the login button

(ngSubmit)="login()"

complete login.component.html.

<div class="login__container">
  <div class="container">
    <div [hidden]="submitted">
        <form class="form-signin"  (ngSubmit)="login()" #loginForm="ngForm">
            <div *ngIf="message !==''" class="alert alert-danger alert-dismissible" role="alert">
              <button type="button" class="close" data-dismiss="alert"aria-label="Close"><span aria-hidden="true">&times;</span></button>
                {{message}}
              </div>
            <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
            <label for="inputEmail" class="sr-only">Email address</label>
            <input type="email" name="email" id="inputEmail" class="form-control" placeholder="Email address" [(ngModel)]="loginData.email" [ngModelOptions]="{standalone: true}"  #email="ngModel" required minlength="6" #spy>
            <div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger">
              <div *ngIf="email.errors.required">
                Email is required.
              </div>
              <div *ngIf="email.errors.minlength">
                email must be at least 6 characters long.
              </div>
            </div>
            <label for="inputPassword" class="sr-only">Password</label>
            <input type="password" id="inputPassword" class="form-control" [(ngModel)]="loginData.password" [ngModelOptions]="{standalone: true}" placeholder="Password" required>
            <div class="checkbox mb-3">
              <label>
                <input type="checkbox" value="remember-me"> Remember me
              </label>
            </div>
            <button class="btn btn-lg btn-primary btn-block" [disabled]="!loginForm.form.valid" type="submit">Sign in</button>
          </form>
    </div>
    
  </div>
</div>

Adding the logout method to the navbar component

Open up the navbar.component.ts and import the authservice and the router to navigate to the login route after the logout is triggered.

import {Router} from '@angular/router';
import { AuthService } from '../auth.service';

create a variable called authUser to hold the authenticated user and set it to null

  private authUser:object = null;

instantiate both import in the constructor method and use the authservices' authAnnouncer method to get the present user and set it to the authUser variable

 constructor(
    private router: Router, 
    private authService: AuthService,
  ) {
    authService.authAnnouncer$.subscribe(
      user => {
        this.authUser = user;
      }
    )

   } 

Lastly we create the logout method which uses the auth service. When triggered we navigate to the login route.

logout () {
    this.authService.logOut();
    this.router.navigate(['login']);
  }
complete navbar.component.ts
import { Component, OnInit, Input } from '@angular/core';
import {Router} from '@angular/router';
import { AuthService } from '../auth.service';

@Component({
  selector: 'app-navbar',
  templateUrl: './app-navbar.component.html',
  styleUrls: ['./app-navbar.component.sass'],
})
export class AppNavbarComponent implements OnInit {

  private authUser:object = null;

  
  constructor(
    private router: Router, 
    private authService: AuthService,
  ) {
    this.cartItemsCount = 0
    authService.authAnnouncer$.subscribe(
      user => {
        this.authUser = user;
      }
    )

   } 

  ngOnInit() {

  }

  logout () {
    this.authService.logOut();
    this.router.navigate(['login']);
  }

    
}

Finally we update our navbarcomponent.html to call the method and display differently when there is an authenticated user.

<ul class="navbar-nav  mr-4">
      <li  class="nav-item active " *ngIf="authUser" >
        <a class="nav-link"  >Logout</a>
      </li>
      <li   class="nav-item active" *ngIf="!authUser" >
          <a class="nav-link" routerLink="/login">Login</a>
        </li>
      <li   class="nav-item active" *ngIf="!authUser">
        <a class="nav-link" routerLink="/signup">Signup</a>
      </li>
    </ul>

In the links, we only show a logout button when the user is logged in i.e, there is a jwt token and a user object in the local storage else we show the login and signup button if none of the former available.
finally we add the login handler to the form.
<a class="nav-link" (click)="logout()" >Logout</a>

Complete navbar.component.html
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
  <a class="navbar-brand" href="#">Voltron</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="collapse navbar-collapse" id="navbarsExampleDefault">
    <ul class="navbar-nav mr-auto ">
      <li class="nav-item active">
      </li>
    </ul>
    <ul class="navbar-nav  mr-4">
      <li  class="nav-item active " *ngIf="authUser" >
        <a class="nav-link"  (click)="logout()" >Logout</a>
      </li>
      <li   class="nav-item active" *ngIf="!authUser" >
          <a class="nav-link" routerLink="/login">Login</a>
        </li>
      <li   class="nav-item active" *ngIf="!authUser">
        <a class="nav-link" routerLink="/signup">Signup</a>
      </li>
    </ul>
    <div  class="mr-4 text-white">
        <a routerLink="/items/order"><i class="fa fa-cart-arrow-down"><span class="badge badge-light">{{ cartItemsCount }}</span></i></a>
    </div>
  </div>
</nav>

Conclusion

Finally, we are done building building the authentication system and toggling the display on the navbar component . In the next tutorial, we are going work on pushing our item into a cart using a cart service.

Curriculum

Resources

Sort:  

Thank you for your contribution.

Looking forward to your upcoming tutorials.

Your contribution has been evaluated according to Utopian rules and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post,Click here


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Hey @sirfreeman
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Contributing on Utopian
Learn how to contribute on our website or by watching this tutorial on Youtube.

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Kindly follow back and upvote

Coin Marketplace

STEEM 0.17
TRX 0.13
JST 0.027
BTC 59046.50
ETH 2654.73
USDT 1.00
SBD 2.50