Websites with Angular and Less CSS tutorials
7. The login component
av_timer8 minute read

So is this the tutorial in which you accidentally reveal your password?

  1. Topics of discussion
  2. Creating the login component
  3. Closing the navigation menu and styling the component
  4. Perfroming the login operation

1. Topics of discussion

In this tutorial, we are going to create a login component alongside all the necessary operations required to perform a login. So, let's have some fun.

2. Creating the login component

Now that we have a way in which we can navigate through our website, I figured it's time we added some routes and pages on it. And what better way to start it than creating a login component that allows a user to...well, you know, log in to your website and stuff.

First thing's first though, let's create a new module:

ng g m components/Authentication

Wait wait wait, hold up...why are we just using letters now? Is Angular CLI that smart? And the answer is yes. You can use short-hand commands for generating components, by understanding the short versions for each element you want to create. It goes like this:

  • you can write g instead of generate
  • you can write m instead of module
  • you can write c instead of component
  • you can write s instead of service

Now, let's generate the login component:

ng g c components/authentication/Login -m=Authentication --export

As you can see, we have also substituted --module for -m. Careful as there is only one dash sign in -m. Now that we have the component, let's add a route to it and put it in our navigation menu. First, let's start by modifying our app-routing.module.ts file.

While we are on the subject, a very useful command in Visual Studio Code is Ctrl+P, which opens a small dialog in which you can enter a file name, select the one that matches what you want to open, press Enter and voila, you have your file. In our case, you can press Ctrl + P, type app-routing.module.ts and press Enter. After you have the file opened, put the following code in it:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './authentication/login/login.component';

const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

What we did here is tell Angular that it should display the LoginComponent whenever we visit the login route. Now, let's add the route to our navigation component html page. Replace the existing mat-nav-list element in your app-navigation.html page with the following:

<mat-nav-list class="navigation-list">
  <a class="navigation-item" mat-list-item routerLink="/login" routerLinkActive="active">
    <mat-icon class="material-icons">person</mat-icon><span class="navigation-item-label">Login</span>
  </a>
</mat-nav-list>

In order to make sure our application runs successfully, let's add our NavigationModule to the imports section in app.module.ts:

imports: [
  BrowserModule,
  AppRoutingModule,
  NavigationModule,
  AuthenticationModule
]

Visual Studio Code will probably now complain that it cannot find the NavigationModule. That's because you have to import it. You can do it manually or you can use the Ctrl + . command to add the import. You may have to position your cursor on that line in order for the command to work. Also remember that for some reason it sometimes does not work for packages whose name start with @, e.g. @angular. I have no idea why this is the case.

3. Closing the navigation and styling the component

If you attempt to build and serve the application and then go to the navigation menu and click on Login, the first thing you'll notice is that the navigation menu remains open. Let's solve this issue by modifying the app-navigation.component.ts file as follows:

import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { MatSidenav } from '@angular/material';
import { Router, NavigationStart } from '@angular/router';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-navigation',
  templateUrl: './app-navigation.component.html',
  styleUrls: ['./app-navigation.component.less']
})
export class AppNavigationComponent implements OnInit, OnDestroy {

  isOpen: boolean;
  private subscription: Subscription;
  @ViewChild('drawer') drawer: MatSidenav;

  constructor(private router: Router) {
    this.subscription = this.router.events.subscribe(event => {
      if (event instanceof NavigationStart) {
        this.isOpen = false;
        this.drawer.close();
      }
    });
  }

  ngOnInit() {
    this.isOpen = false;
  }

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

}

Quite a lot of stuff there. So, alongside OnInit, we also implement OnDestroy, which allows us to do some stuff when the component is destroyed, which in case of our navigation component happens only when we leave the site. Now, what about that subscription variable? First off, we made it private because we do not want it to be exposed anywhere since we only use it inside the .ts file. It's still visible inside the html of the component if you want, but that's about it.

Subscription object allows you to subscribe to an Observable object. An Observable object is a certain type of object in Typescript whose values might change. Subscribing to such an object allows you to run some code each and every time a change occurs on that object. And as you can see, in our case, we want to do something each time an event of type NavigationStart occurs.

In other words, each and every time we navigate via a route defined in the navigation menu, we want the navigation menu to close. Hence why we set the isOpen variable to false and we call the close() method from the drawer object.

Finally, we need to make sure to unsubscribe from any such subscriptions to prevent a memory leak. Hence why we implemented the OnDestroy interface.

Now, if you rebuild and serve your application again, the navigation should close when going to the login page.

Let's move on to the login.component.html file and add the following code in it:

<div class="container container-fluid">
  <form [formGroup]="loginForm" (ngSubmit)="login()">
    <div class="form-group">
      <div class="col-12">
          <h2>Enter your login details below:</h2>
      </div>
    </div>
    <div class="form-group">
      <div class="col-12">
        <label for="username">Username:</label>
        <input id="username" class="form-control" placeholder="Enter your username here..." formControlName="username" />
      </div>
    </div>
    <div class="form-group">
      <div class="col-12">
        <label for="password">Password:</label>
        <input id="password" type="password" class="form-control" placeholder="Enter your password here..." formControlName="password" />
      </div>
    </div>
    <div class="form-group">
      <div class="col-12 text-right">
        <button class="btn btn-sm login-button" type="submit" [disabled]="shouldDisableButton()">Login</button>
      </div>
    </div>
  </form>
</div>

So what we have here is basically a form, with some bootstrap classes (container, container-fluid, form-group, col-12 etc.). However, the more intereseting part lies in the Angular mark-up used:

  • [formGroup] - an attribute used to assign a FormGroup object to this form (more on that below)
  • (ngSubmit) - an attribute used to assign a method to call when our form is submitted
  • formControlName - an attribute used to assign a variable from the formGroup above to the given html input element

As you can see, each of this attributes has a value assigned to it. Where are these values located though? The answer is simple: in the login.component.ts file:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';

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

  loginForm: FormGroup
  constructor(private fb: FormBuilder) {
    this.loginForm = this.fb.group({
      username: ["", Validators.required],
      password: ["", Validators.required]
    });
  }

  login(): void {
    alert("Login Button clicked");
  }

  ngOnInit() {
  }

}

As you can see, we have defined the loginForm variable of type FormGroup and we initiliaze it in the constructor. Notice how we add two variables to it, with the names of those variables used in the html code. We also tell Angular that we want these variables to be required. This will come into play in the next section.

Finally, we also define the login() method, which is called when we click the Login button, which we defined as a submit button.

Let's add a bit of styling to our page, in the login.component.less file:

label {
  font-weight: bold;
}

.login-button {
  background-color: #062739;
  color: #fff;
}

Before we move on, one thing I forgot to mention. In order for everything to work correctly, we need to import two modules in the authentication.module.ts file. The two modules are FormsModule and ReactiveFormsModule. Go ahead and add them to your imports

4. Performing the login operation

Performing the login operation is an easy enough task...sort of. We just need to take the info the user provides in the login form and verify to see that the username-password combination is correct.

How we go about this in this tutorial is rudimentary. By that I mean we don't have any encryption or anything since it is a bit out of scope for this course. But reast assured, such tutorials will be in place soon (depending on when you're reading this tutorial).

In order to have some organization in our code, we are going to use a service to handle any and all authentication operations. So, let's generate it:

ng g s components/authentication/Authentication

Once that is complete, go ahead and add the service to the providers array of the authentication.module.ts file:

providers: [AuthenticationService]

Now, modify the contents of the authentication.service.ts file as follows:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {

  private users: any = [
    {username: 'almorecipes', password: 'test123'}
  ]
  constructor() { }

  public login(username: string, password: string): void {
    var existsUser =  this.users.filter(c => c.username === username && c.password === password).length > 0;
    if (!existsUser) {
      alert("Invalid username/password");
    } else {
      sessionStorage.setItem('recipe-user', username);
      alert("You are now logged in!");
    }
  }
}

What we do in this is define a list of users that can log in to this website. We then define a method that uses the filter method to check if the users array contains an entry that has the exact same username and password values that are received. If it does, then it sets a session cookie for the given user so that we know if he is logged in. Otherwise, it prompts the user that the username/password combination is invalid.

A couple of things to be mentioned here:

  • the code presented here is the most basic way of doing a login operation
  • in a real-life app, you will no doubt have a server to which you send the user credentials for validation
  • you will also have some sort of encryption applied to the password before it is verified
  • finally, more often than not the server will send back an authentication token which will be used as a cookie; the authentication token will contain basic information regarding the user such as his username and other relevant information (status, access level etc) but never the password

Right, now that we have the login method, let's reference our service in the login component and call the method. We will also add a nice bit of validation to our form to prevent useless submissions or multiple submissions:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { AuthenticationService } from '../authentication.service';

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

  loginForm: FormGroup;
  loading: boolean = false;
  constructor(private fb: FormBuilder, private authService: AuthenticationService) {
    this.loginForm = this.fb.group({
      username: ["", Validators.required],
      password: ["", Validators.required]
    });
  }

  login(): void {
    this.loading = true;
    this.authService.login(this.loginForm.value.username, this.loginForm.value.password);
    this.loading = false;
  }

  public shouldDisableButton(): boolean {
    return !this.loginForm.valid || this.loading;
  }

  ngOnInit() {
  }

}

Now, let's modify our Login button as follows:

<button class="btn btn-sm login-button" type="submit" [disabled]="shouldDisableButton()">Login</button>

As you can see, we added a new method that checks whether or not we should disable a button. It checks if the form is valid (based on the validators from the previous section) and if there is no other login operation in progress. If any of those conditions is true, your Login button will be disabled.

Other than that, we just invoke the login() method from the AuthenticationService service, which we injected in the constructor of our LoginComponent.

We will revisit this code in another tutorial in order to clean it up a bit and add a bit more logic but for now, this concludes our tutorial. Next time, we'll get around to creating a Register Page component. See you then.

COMMENTS
No comments yet...