Angular - Creating a Single Page Application

 



Overview

Assuming that the reader has read my first post on Angular we should all now be familiar with the building blocks of an Angular application. Therefore, to fully grasp the potential or in my case the frustration of using the Angular framework I am now going to go through how I built my first Angular application. Notice that I said frustration but bear in my mind that when learning new things there is always going to be a learning curve and making mistakes along the way facilitates stronger understanding and knowledge of concepts even if it may result in hours of hair pulling and shouting. For my first application I decided to convert the Web Development (CPRG-352) course project I made back in my 3rd semester at SAIT. This application was essentially a simple CRUD application that allows a user to log into their account and enter information of items in their home such as the item name, price, and category. It was built as a Java Web Application using a pure JSP/Servlet architecture with JPA to manage the MySQL database in the backend. Fair warning here, if you weren’t paying attention, Angular is a front-end framework therefore I will be focusing on how I created the front end. How you choose to implement the backend is up to you, I will however present some options and what I used later on. I will also provide a link to the end result of the website. With that said let us get started on creating our first Angular application.



Login interface of original application


Inventory interface of original application


The Set Up

The first step is of course to generate the necessary project files with Angular CLI. Just like we learned in the first post all it takes is a simple command, however this time I added the routing switch in order to add the script for configuring the routes to navigate our application, more on this later.

ng new <project name> --routing

Next, to make the app look prettier since the older version left a lot to be desired, I used bootstrap CSS which will allow me to apply well-polished styling by simply adding classes to the HTML. To add Bootstrap to your application you can simply add the link to the index.html file or download the files on the website. 

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Home Nventory</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
</head>
<body>
  <app-root></app-root>
</body>
</html>

For this demonstration there are three components to create Login, Sign-up, and Inventory (hint: use Angular CLI). Once the components have been created you can start configuring the routes for the application which is done through the app-routing.module.ts file. This file has an array of routes where we can add paths or URLs in order to navigate to a particular component. Each component will have its own URL plus an entry for where the user will be redirected for the root path (code for the paths). 

app-routing.module.ts

const routes: Routes = [
  {path: 'login', component: LoginComponent},
  {path: 'signup', component: SignUpComponent},
  {path: 'inventory', component: InventoryComponent},
  {path: '', redirectTo: '/login', pathMatch: 'full'}
];

In order for the links to work we will be adding the router-outlet tag to the app component HTML. At this point if we type the URL in the browser it should take us to the component and display what is in that component’s HTML file. 

app.component.html

<h1>Home nVentory</h1>
<router-outlet></router-outlet>

Forms

Both the Login and Sign-up components are going to be forms and so you will just be adding the necessary form tags and input tags into the HTML file as with any forms. As you can see, I added the appropriate bootstrap classes to make the form more presentable

login.component.html

<h1>Login</h1>
<form class="m-3">
  <div class="form-group">
    <label for="">Email</label>
    <input type="text" class="form-control">
  </div>

  <div class="form-group">
    <label for="">Password</label>
    <input type="password" formControlName="password" class="form-control">
  </div>

  <div class="row">
    <div class="col-8"><button class="btn btn-primary m-3 
 btn-lg">Login</button>
  </div>
</form>

When a user is entering input into the form, we want the page to display the appropriate visual cues to help the user with filling in valid data. This is where the data binding concept is going to be helpful. For this purpose, I used the FormBuilder service which can be accessed through the ReactiveFormsModule, this needs to be imported in the app module. We can then inject the service in our components and assign a variable with the type FormGroup which will store our form fields. The ngOnInit() method which is executed once the component  has been built, is used to initialize this variable with the names of the form fields, default values if any, and validators. 

login.component.ts

loginForm: FormGroup;
  constructor(private fb: FormBuilder) { }
  ngOnInit(): void {
    this.loginForm = this.fb.group({
      email: ['', Validators.required],
      password: ['', Validators.required]
    });
  }

login.component.html

<form class="m-3" [formGroup]="loginForm">

. . .

<div class="form-group">
    <label for="">Email</label>
    <input type="text" class="form-control" formControlName="email" 
     [class.is-invalid]="email.invalid && email.touched">
    <small [class.d-none]="email.valid || email.untouched" 
     class="text-danger">Enter your email</small>
</div>

login.component.ts

get email() {
    return this.loginForm.get('email');
  }


Finally, we will add the submit event to the form so that the appropriate method is executed when the user submits. For now we can leave the login() method empty as I will explain how to implement that later on. Repeat the same process for the Sign-up component.

login.component.html

<form class="m-3" [formGroup]="loginForm" (ngSubmit)="login()">

login.component.ts

login() {
    //implement service 
  }

Here is a tutorial video on how to create Angular forms. 



Navigation

To demonstrate how to implement links in the application, I added a navigation bar in the app component using bootstrap classes.  The links will be routed to the login and signup components simply by providing the URLs created to the href attribute.

app.component.html

<nav class="navbar navbar-expand-lg navbar-dark  bg-dark">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Home nVentory</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" 
       data-bs-target="#navbarSupportedContent" 
       aria-controls="navbarSupportedContent" aria-expanded="false" 
       aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link" aria-current="page" href="#">Home</a>
          </li>
          <li class="nav-item" [hidden]="authenticated()">
            <a class="nav-link" href="/login">Login</a>
          </li>
          <li class="nav-item" [hidden]="!authenticated()">
            <a class="nav-link" href="/inventory">Inventory</a>
          </li>
          <li class="nav-item" [hidden]="!authenticated()">
            <a class="nav-link" (click)="logout()">Logout</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>
    <div class="container-fluid">
       <h1 class="display-1"><b>Home nVentory</b></h1>
       <router-outlet></router-outlet>
   </div>
</div>

To send a user to a different component when a specific event occurs the Router service from the AppRoutingModule needs to be used. For example, after the user has been authenticated, we want to send the user to the inventory component.  In the login component class, we can inject the Router service then use this service to redirect the user to the inventory URL. 

login.component.ts

constructor(private _router: Router, private fb: FormBuilder) { }

. . . 

login() {
    //implement login service
this._router.navigateByUrl('/inventory'));
  }

Services

To authenticate and get the data that will be displayed in the inventory page we need to create service components that will connect to some kind of back end.  As mentioned earlier the implementation of the back end is beyond the scope of this post and is up to you. One option is to store your data in JSON format and just have your services accessing that JSON file. Another option is creating an Express server which can be done through nodejs, that connects to a database like MySQL. The option that I went with was thinking of it as an opportunity to learn something new therefore I took the initiative to learn how to create a RESTful API using Spring Boot as my backend. Therefore, the rest of the demonstration will show how to retrieve data from an API. To send http GET and POST requests add the HttpClientModule in the app module. For this demonstration I will explain how to request the items a particular logged in user has from an API. I created a UserService class that injects the HttpClient service and has a getUser method that takes in the user’s email and creates a GET request to the API (identified by the URL) as well as passes the email as a parameter. 

user.service.ts

export class UserService {
  _url = "http://localhost:8080/inventory/api/v1/user";

  constructor(private _http: HttpClient) { }

  getUser(email){
    let requestResource = this._url + "/"+ email;
    return this._http.get<User>(requestResource, email);
  }
}

In the inventory component the UserService service is injected, and we can then subscribe to the getUser method. If successful, the API’s response should be in JSON format and parsed into an Observable object that we can assign to a variable. This variable can be used to display the contents of data sent by the API, in this case the items the user owns. 

inventory.component.ts

constructor(private _userService: UserService) { }

  ngOnInit(): void {
    let email = localStorage.getItem('email');
    this._userService.getUser(email).subscribe(data => this.user = data,
      error => this.errorMsg = error);
  }

inventory.component.html

<table class="table table-striped table-hover table-light">
        <thead class="table-light">
            <tr>
                <th>Id</th>
                <th>Category</th>
                <th>Item Name</th>
                <th>Price</th>
                <th>Owner</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr *ngFor="let item of user.items; let i = index">
                <td>{{item.id}}</td>
                <td>{{item.category.categoryName}}</td>
                <td>{{item.itemName}}</td>
                <td>{{item.price}}</td>
                <td>{{item.owner}}</td>
                <td><button class="btn btn-danger"></button></td>
            </tr>
        </tbody>
    </table>

To authenticate I created a login service that sends a request to the API and if the API responds with the appropriate data, the credentials are stored as cookies. These are later used as headers every time the logged in user sends a request to the API.

login.service.ts

authenticate(credentials, callback) {
        const headers = new HttpHeaders(credentials ? {
            authorization: 'Basic ' + btoa(credentials.email + ':' + 
            credentials.password)
        } : {});
        this._http.get(this._url, { headers: headers }).subscribe(response => {
            if (response['name']) {
                console.log(response);
                this.authenticated = true;
                credentials.authdata = window.btoa(credentials.email + ':' + credentials.password);
                localStorage.setItem('currentUser', JSON.stringify(credentials));
            } else {
                this.authenticated = false;
            }
            return callback && callback();
        });
    }

login.component.ts

login() {
    this._loginService.authenticate({
      email: this.loginForm.get('email').value,
      password: this.loginForm.get('password').value
    }, () => this._router.navigateByUrl('/'));
  }

Now that we know how to use services and how to send http requests, we can pretty much follow the same process to implement the rest of the CRUD operations. To finish the application, add the functionality of adding, updating, and deleting an item and creating a new user. 

Closing remarks 

Allow me a moment to gush about bootstrap for a bit. If you do not know about it already Bootstrap is a library of CSS styles and JavaScript functions that help quickly design and customize responsive websites. If you are not a huge fan of CSS like me then I implore you to utilize this wonderful tool.  I experienced some difficulties working with the security and authentication portion of creating this application. In hindsight creating an API simultaneously might have been ambitious and implementing a fake back end instead might have allowed me to focus on Angular. As a tip, perhaps store your data in a JSON file first or just ignore the authentication altogether to experience less frustration. If you would like to have a look at the completed result check out the GitHub repository and the deployed website on the links below. If there are any questions for me do not hesitate to post a comment below. 

https://github.com/jlomibao1995/home-inventory-angular 

https://getbootstrap.com/

http://www.homenventory.tk/home



Comments

Popular posts from this blog

Angular - Introduction to Front End Framework