Advance single page application with Angular and Bootstrap


Advance single page application with Angular and Bootstrap

Recipe ID: hsts-r47


Recipe Overview

To build single page applications (SPAs) using Angular 2, we need to learn how to implement routing in Angular 2. Angular 2 comes with built-in routing APIs, which are very powerful, feature rich, and easy to use. In this recipe, we will build a basic search engine template to demonstrate routing in Angular 2. We won't be building   a complete search engine because that's out of the scope of this recipe. We will use Bootstrap 4 to design the search engine template. At the end of this recipe, you will be comfortable with building SPAs using Angular 2.

Before working on this tutorial, reading Comprehensive overview of Angular 2 architecture and features is highly recommended.

In this recipe, we will cover the following topics:

  1. Routing in Angular 2
  2. The built-in HTTP client provided by Angular 2
  3. Generating random textual data using the Chance.js library

Setting up the project

Follow these steps to set up your project:

  1. In the exercise files of this recipe, you will find two directories, initial and final. The final directory contains the final search engine template whereas the initial directory contains the files to quickly get started with building the search engine template.
  1. In the initial directory, you will find app.js and package.json. In the

package.json file, place this code:
{
"name": "SearchEngine-Template", "dependencies": {
"express": "4.13.3",
"chance": "1.0.3"
}
}
Here, we are listing Express.js and Chance.js as dependencies. Express will be used to build the web server whereas Chance.js will be used to generate random textual data to populate the template's search results.

  1. Now, run npm install inside the initial directory to download the packages.

Inside the initial directory, you will find a directory named public, inside which all the static assets will be placed. Inside the public directory, you will find the componentTemplates, css, html, and js directories.
Inside the css directory, you will find bootstrap.min.css; index.html inside the html directory; and finally, index.js, angular2-all.umd.js, angular2-polyfills.js, and Rx.umd.js inside the js directory.

  1. In index.html, place this starting code to load Angular, Bootstrap, and the

index.js file:
<!doctype html>
<html>
<head>
<title>Search Engine Template</title>
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css">
</head>
<body>

<script src="/js/angular2-polyfills.js"></script>
<script src="/js/Rx.umd.js"></script>
<script src="/js/angular2-all.umd.js"></script>
<script src="/js/index.js"></script>
</body>
</html>
This code is self-explanatory.

  1. Now, in the app.js file, place this code:

var express = require("express"); var app = express();
app.use(express.static( dirname + "/public")); app.get("/*", function(httpRequest, httpResponse, next){
httpResponse.sendFile( dirname + "/public/html/index.html");
})

app.listen(8080);
Here as well, most of the code is self-explanatory. We are simply serving
index.html regardless of what the HTTP request path is.

Configuring routes and bootstrapping the app

In SPA, the routes for our app are defined in the frontend. In Angular 2, we need to define the paths and a component associated with the path that will be rendered for that path.
We provide the routes to the root component, and the root component displays the component bound to the route.
Let's create the root component and routes for our search engine template:

  1. Place this code in the index.js file to to create the root components

and routes:
var AppComponent = ng.core.Component({ selector: "app",
directives: [ng.router.ROUTER_DIRECTIVES], templateUrl: "componentTemplates/app.html"
}).Class({
constructor: function(){}
})

AppComponent = ng.router.RouteConfig([
{ path: "/", component: HomeComponent, name: "Home" },
{ path: "/search-result", component: SearchResultComponent, name: "SearchResult" },

{ path: "/*path", component: NotFoundComponent, name: "NotFound" }
])(AppComponent);

ng.platform.browser.bootstrap(AppComponent, [ ng.router.ROUTER_PROVIDERS, ng.core.provide(ng.router.APP_BASE_HREF, {useValue : "/" })
]);

  1. Now, create a file named app.html in the componentTemplates directory and place this code in it:

<nav class="navbar navbar-light bg-faded">
<ul class="nav navbar-nav">
<li class="nav-item">
<a class="nav-link" [routerLink]="['Home']">Home</a>
</li>
</ul>
</nav>
<router-outlet></router-outlet>
Here is how this code works:

  1. At first, we create the root component, called AppComponent. While creating the root component, we add the ng.router.ROUTER_DIRECTIVES directive to it, which lets us use the routerLink directive.
  2. Then, we use ng.router.RouteConfig to configure the routes for our application. We are providing an array of routes as an argument to the ng.router.RouteConfig method. A route consists of a path, component, and the name of the route. The paths can be static, parameterized, or wildcard, just like Express route paths. Here, the first route is for the home page, second for displaying the search result, and finally, the third for handling invalid URLs, that is, URLs for which routes are not defined. The ng.router.RouteConfig method returns a function that takes the root component and attaches the routes to it.
  3. We then initialize the application. While initializing the app, we're passing the ng.router.ROUTER_PROVIDERS provider, which will be used to create instances of various services related to routing. Also, we are providing a custom provider, which returns the / character when an instance of the ng.router.APP_BASE_HREF service is requested. ng.router.APP_BASE_ HREF is used to find the base URL of the app.
  1. In the AppComponent template, we are displaying a navigation bar. The navigation bar has an anchor tag that doesn't have an href attribute; instead, we are using the routerLink directive to assign the redirect link so that when clicked on, instead of a complete page reload, it only changes the URL and component. And finally, <router-outlet> is what displays the component based on the current URL.

 

Generating random search results

To populate our template, we need to generate some random search result data. For this, we can use the Chance.js library. We will generate random data on the server side, not on client side, so that we can later demonstrate how to make an HTTP request using Angular 2.
Chance.js is available for both client-side and server-side JavaScript. We earlier downloaded the Chance.js package to use with Node.js. Here is the code to generate random data. Place it in the app.js file above the /* route so that /* doesn't override the random data route:
var Chance = require("chance"), chance = new Chance();
app.get("/getData", function(httpRequest, httpResponse, next){ var result = [];
for(var i = 0; i < 10; i++)
{
result[result.length] = { title: chance.sentence(), desc: chance.paragraph()
}
}

httpResponse.send(result);
})
Here, we first create a route for the /getData path, which sends an array of search results as a response. The route callback uses chance.sentence() to generate random titles for the search result and chance.paragraph() to generate a description.

Creating route components

Let's create HomeComponent, SearchResultComponent, and NotFoundComponent. Before that, let's create a component to display the search form. The search form will have a textbox and a search button. Follow these steps:

  1. Place this code in the index.js file, above the AppComponent code:

var FormComponent = ng.core.Component({ selector: "search-form",
directives: [ng.router.ROUTER_DIRECTIVES], templateUrl: "componentTemplates/search-form.html",
}).Class({
constructor: function(){}, ngOnInit: function(){
this.searchParams = { query: ""
};

this.keyup = function(e){ this.searchParams = {
query: e.srcElement.value
};
};
}
})

  1. Now, create a file named search-form.html in the componentTemplates

directory, and place this code in it:
<div class="m-a-2 text-xs-center">
<h1>Search for Anything</h1>
<form class="form-inline m-t-1">
<input (keyup)="keyup($event)" class="form-control" type="text" placeholder="Search">
<a [routerLink]="['SearchResult', searchParams]">
<button class="btn btn-success-outline" type="submit">Search</button>
</a>
</form>
</div>

This is how the code works:

  1. At first, we create a component called FormComponent. It uses the

ng.router.ROUTER_DIRECTIVES directive.

  1. In the template of the component, we display an HTML form. The form has a textbox and button.
  2. We handle the keyup event of the text input box and store the value in the

searchParams.query property.

  1. The button redirects to the SearchResult component. Note that here we are passing searchParams object to routerLink, which becomes the query parameter when redirecting.

Now, let's create the HomeComponent component. This component is displayed on the home page. It displays the search form.
Here is how to create HomeComponent:

  1. Place this code in the index.js file, above the AppComponent code:

var HomeComponent = ng.core.Component({ selector: "home",
directives: [FormComponent],
templateUrl: "componentTemplates/home.html",
}).Class({
constructor: function(){}
})

  1. Now, create a file named search-form.html, and place it in the

componentTemplates directory:
<search-form></search-form>
Here, the HomeComponent code is self-explanatory.

  1. Now, let's create the SearchResultComponent component. This component should display the search form and the search result below it. It should fetch the result by making an HTTP request to the server. Here is the code for the SearchResultComponent. Place it in the index.js file, above the AppComponent code:

var SearchResultComponent = ng.core.Component({ selector: "search-result",
directives: [FormComponent], viewProviders: [ng.http.HTTP_PROVIDERS],
templateUrl: "componentTemplates/searchResult.html"
}).Class({

constructor: [ng.router.RouteParams, ng.http.Http, function(params, http) {
this.params = params; this.http = http; this.response = [];
}],
ngOnInit: function(){
var q = this.params.get("query"); this.http.get("getData").subscribe(function(res){
this.response = JSON.parse(res._body);
}.bind(this));
}
})

  1. Now, create a file named searchResult.html and place it in

componentTemplates. Place this code in the file:
<style>
ul
{
list-style-type: none;
}
</style>

<div class="container">
<search-form></search-form>
<div class="m-a-2 text-xs-center">
<ul>
<li *ngFor="#item of response" class="m-t-2">
<h4>{{item.title}}</h4>
<p>{{item.desc}}</p>
</li>
</ul>
</div>
</div>

 


This is how the code works:

  1. Here, we are providing the ng,http.HTTP_PROVIDERS provider, which is used when using the HTTP client service provided by Angular 2. Using the HTTP client service, we can make HTTP requests.
  2. In the constructor property, we are injecting the HTTP service along with the ng.router.RouteParams service, which is used to obtain the query parameters of the current URL.
  3. In the ngOnInit method, you can see how to make a GET request using the HTTP service and also how to get the query parameters using the ng.router.RouteParams service.
  4. In the template of the component, we are displaying the fetched search result using the ngFor directive.

 

Now, let's create NotFoundComponent. Here is the code for that:

  1. Place this code in the index.js file, above the AppComponent code:

var NotFoundComponent = ng.core.Component({ selector: "name-search",
templateUrl: "componentTemplates/notFound.html"
}).Class({
constructor: function(){}
})

  1. Now, create a file named notFound.html and place it in the

componentTemplates directory. Place this code inside the file:
<div class="container">
<div class="m-a-2 text-xs-center">
<h1>The page your are looking for is not found</h1>
</div>
</div>
The code is self-explanatory.


Testing the template

To test the template, we will follow these steps:

  1. Inside the initial directory, run the node app.js command.
  2. Now, in a browser, open the http://localhost:8080/ URL. You should see this output:

Learn web design with Angular

  1. Now, type something in the search box and click on the Search button. You should then see this output:

Learn web design with Angular

  1. Now, enter an invalid path in the address bar. You should be able to see this output:

Learn web design with Angular

Routing life cycle methods

When a path matches a component, Angular 2 activates the component, and when the path changes, Angular 2 deactivates it. When we say that a component has been activated, it means that Angular 2 has created an instance of the component, that is, called the constructor method of the component, whereas when we say a component has been deactivated, it means the component has been removed from the DOM and instance is deleted.
The methods of a component that are called while activating or deactivating it are called routing lifecycle methods.
Here is the list of routing lifecycle methods:

  1. CanActivate: This hook is invoked before activating the component. It should return a boolean value or a promise indicating whether to activate the component.
  2. routerOnActivate: This method is invoked after the component has been activated.
  3. routerCanReuse: This method is invoked to find out whether to reuse the previous instance of the component when the next URL change is the same URL again. It should return a boolean value or a promise indicating whether to reuse. It's invoked only if an instance had been created earlier.
  4. routerOnReuse: This method is invoked if the component is being reused. It's called after routerCanReuse.
  5. routerCanDeactivate: This method is invoked before deactivating the component. It should return a boolean value or a promise indicating whether to deactivate the component.
  6. routerOnDeactivate: This method is invoked after the component has been deactivated.

Let's look at a code example of the routing lifecycle methods. Replace the
HomeComponent code with this:
var HomeComponent = ng.core.Component({ selector: "home",
directives: [FormComponent],
templateUrl: "componentTemplates/home.html",
}).Class({
constructor: function(){}, routerOnActivate: function(){
console.log("Component has been activated");
},
routerCanReuse: function(){ console.log("Component can be reused"); return true;
},
routerOnReuse: function(){ console.log("Component is being reused");
},
routerCanDeactivate: function(){ console.log("Component can be deactivated"); return true;
},
routerOnDeactivate: function(){ console.log("Component has been deactivated");
}
})

HomeComponent = ng.router.CanActivate(function(){ console.log("Component can be activated"); return true;
})(HomeComponent);
Now, visit the home page. There, click on the home button again. Now, type something in the search box and click on the Search button. This is the console output you will see:
Component can be activated Component has been activated Component can be reused Component is being reused Component can be deactivated Component has been deactivated

Production mode versus development mode

Until now, we have been running Angular 2 in development mode. The difference between development and production mode is that in development mode, Angular 2 starts change detection immediately after the first run and logs a value has changed after it was checked error if anything changes between the first and second run. This helps locate bugs.
To enable production mode, place this code above the ng.platform.browser. bootstrap() method call:
ng.core.enableProdMode();

 

Summary

In this recipe, we learned routing in Angular 2 by building a basic search engine template. Along with learning routing in depth, we also learned about the Angular 2 HTTP client service as well as how to switch to production mode in Angular 2.
You should now be comfortable with building the frontend of any kind of web application using Angular 2.

 

Here are related articles if you wish to learn more advance topics for web development:

Best practices for securing and scaling Node.JS applications
How Bootstrap 4 extensible content containers or Cards work
Comprehensive guide for migration from monolithic to microservices architecture
Comprehensive overview of Bootstrap 4 features for user interface customizations
Intro to functional reactive programming for advance js web development
Using advance js and webrtc for cross browser communications in real time
Intro to real-time bidirectional communications between browsers and webSocket servers

Junior or senior web developers can also explore career opportunities around blockchain development by reading below articles:

Blockchain Developer Guide- Comprehensive Blockchain Ethereum Developer Guide from Beginner to Advance Level
Blockchain Developer Guide- Comprehensive Blockchain Hyperledger Developer Guide from Beginner to Advance Level

Here are more hands-on recipes for advance web development:
Develop microservices with monolithic core via advance Node.JS
Develop server-side applications using microservices with Seneca.JS
Advance UI development with JS MVC framework and react
Develop advance JavaScript applications with functional reactive programming
Develop advance webcam site using Peerjs and Peerserver with Express.JS
Develop advance bidirectional communication site with websockets and Express.JS

This recipe is developed by Narayan Prusty who is our senior Blockchain instructor.

Related Training Courses

Hands-on Node.JS, MongoDB and Express.js Training
Advance JavaScript, jQuery Using JSON and Ajax
Developing Web Applications Using Angular.JS
Design websites with JavaScript React in 30 hours
Blockchain Certified Solution Architect in 30 hours
Advance JavaScript, jQuery Using JSON and Ajax
Introduction to Python Programming
Object Oriented Programming with UML


Private and Custom Tutoring

We provide private tutoring classes online and offline (at our DC site or your preferred location) with custom curriculum for almost all of our classes for $50 per hour online or $75 per hour in DC. Give us a call or submit our private tutoring registration form to discuss your needs.


View Other Classes!