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
<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
{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
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
<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
</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
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
this.loginForm = this.fb.group({
email: ['', Validators.required],
password: ['', Validators.required]
});
}
login.component.html
login.component.ts
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
login.component.ts
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
<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
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
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
ngOnInit(): void {
let email = localStorage.getItem('email');
this._userService.getUser(email).subscribe(data => this.user = data,
error => this.errorMsg = error);
}
inventory.component.html
<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
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
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.
Comments
Post a Comment