This tutorial will introduce readers to the method of creating an AngularJS application using ES6 JavaScript and Modules.
Introduction
At the end of last year, I was thinking about the future. The future is here. But it is hard sometimes to take the leap and embrace the future. Learning a new skill, and getting good enough to use it for a new project takes a lot of time, and effort. This is the reason why I was reluctant to pick up node.js. The kind of project development it supports often takes too long to master. Most likely, I have to learn five different things at the same time, and the outcome would not be good. The learning would be a distraction to the development. But I want to go forward and use modern JavaScript language for development. So I wondered, is it possible to use ES6 syntax based JavaScript with AngularJS to develop application, and without the hassle with node.js? Turned out, it is possible. This tutorial will discuss how.
Here is what I am trying to do. I want to create a single page AngularJS application, and I want to use ES6 base JavaScript syntax and no node.js. As said before, this is do-able and it is quite easy to accomplish. I did take a few hours to research. Anyways, this tutorial will step you through the implementation, and introduce to you how to write AngularJS with ES6 script syntax.
Sample Application Architecture
The sample application for this tutorial uses Spring Boot to create a web based application. All it does is serve static contents like HTML page, style sheets and JavaScript files to user. It is a single page application, with four navigate-able sub pages. The first sub page is index, it is just a static page for display. The second page will display a string
value from the controller, and a button which the click
event is handled by a method of the controller object. The other two pages are static
and also for display purposes only.
The whole idea is to show how to create an JavaScript object using ES6 script. And this controller is connected (in plain terms) to the view (the page templates), and can exchange data between view and the controller. Also, the application will show how to setup the navigation of the page templates. Finally, tying these two together into one application. These are the most essential functionalities for AngularJS applications. If these can be done with ES6 scripts, then a full application can be written use the same syntax. The hard part is figuring how. Turned out, all these were not hard. All that is needed is a little research.
The Entry Page
I will start with the entry page. There is one important aspect that I have to point out. In order to use ES6 script syntax with my application, I need to add my JavaScript files as modules, not as normal script file. Let me show you the difference. This is a normal script file I have added to the HTML page:
...
<script type="text/javascript" src="/assets/jquery/js/jquery.min.js"></script>
...
In order to add the JavaScript files as modules, I have to do this:
...
<script type="module" src="/assets/app/js/TestController.js"></script>
...
This is necessary because according to the documentation, Chrome and FireFox can execute ES6 syntax compatible scripts if the file is added as a module. There you go. This is the first step in order to use ES6 based syntax.
Let me show you the rest of this entry page. The reason we call these AngularJS application as single page application is because there is just one page, and the content of this page changes based on the templates that are placed into this page. This entry page is the index.html file found under the subdirectory src/main/resources/static/. There is a very basic navigation menu:
<div class="row">
<div class="col-0xs-12">
<ul class="nav nav-pills">
<li role="presentation"><a href="./#!/">Home</a></li>
<li role="presentation"><a href="./#!/red">Red Page</a></li>
<li role="presentation"><a href="./#!/green">Green Page</a></li>
<li role="presentation"><a href="./#!/blue">Blue Page</a></li>
</ul>
</div>
</div>
This is a very simple navigation menu, only 4 links. If you are working with AngularJS for the first time, the way navigation works is using links like this: "./#!/<angularjs-links>", i.e. "./#!/blue" These are the URL rewrite rule which AngularJS router can recognize and properly render the pages.
Since we are using the AngularJS router and not the fancy ui-router (a third party component that works with AngularJS), we need to designate an area where the sub pages can be rendered. This is how:
<div class="row">
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-body">
<ng-view></ng-view>
</div>
</div>
</div>
</div>
The pages will be rendered as the child element of the tag called <ng-view/>
.
Every AngularJS application has an entry point, an ngModule
that is designated as the ngApp
. This is defined at the top of the HTML page like this:
<div class="container top-margin" ng-app="startup">
...
</div>
At last, I add the JavaScript files and modules to this web page. Like this:
...
<script type="text/javascript" src="/assets/jquery/js/jquery.min.js"></script>
<script type="text/javascript" src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/assets/angularjs/1.7.5/angular.min.js"></script>
<script type="text/javascript" src="/assets/angularjs/1.7.5/angular-resource.min.js"></script>
<script type="text/javascript" src="/assets/angularjs/1.7.5/angular-route.min.js"></script>
<script type="module" src="/assets/app/js/app.js"></script>
...
The first few lines are the added JavaScript files for AngularJS source. The last line is the module of the entry of my AngularJS app. I didn't have to list out all the other modules because this file and the subsequent module files are all interconnected. They import each other and create the reference links so there is no need to add every single JS file here.
Besides this base HTML file, there are four more HTML files, each represents a sub page. One of them is also called index.html. It is the default page to show, the other pages are named with a color, and the page designated as "red" has some interactive elements on it, to demonstrate how to create a controller for the page. The other two pages are static. They are used to demo the routing functionalities.
In the next section, I will show you how my JavaScript module code works. This includes the application entry, the routing configuration, and the test controller.
Application Modules
Every AngularJS application must have a module defined, and designated as the value for ngApp
. I have showed how this is done. Let me show you again:
<div class="container top-margin" ng-app="startup">
...
</div>
This "startup
" ngModule
is defined in the file called app.js. And its content looks like this:
import { appRouting } from '/assets/app/js/app-routing.js';
import { TestController } from '/assets/app/js/TestController.js';
let app = angular.module('startup', ["ngRoute"]);
app.config(appRouting);
app.controller("TestController", TestController);
As you can see, this looks somewhat different from the JavaScript code I have used to write. This file represents a module in the sense of ES6. I used the new ES6 syntax to import objects (class object or function) from another module into the current one. The first two lines is to bring single objects from the other modules. The first line imports a function called appRouting
from the module "/assets/app/js/app-routing.js". The second line imports an object of class TestController
. The last three lines are injecting these objects into the AngularJS' startup invocation process. The first line creates an object called "app
". It is an AngularJS module. The module is named as "startup
". This is the name that is referenced by ngApp
on the HTML page. The "app
" invokes two methods. The first is called .config(). This is where I passed in the function appRouting
(imported from the app-routing.js). This basically letting AngularJS call my function to setup the sub path routing. The last line is having the object "app
" invoke the function .controller()
to register the controller with name "TestController
". This is like an IOC container, adding the object as a controller so that the view (HTML page) and the AngularJS internal can reference this controller correctly. There is nothing special with this module, all there is, configuration of the application start up.
Next, I will show you how the appRouting
function is defined. In order to setting up the sub page navigation for the application, I need to inject an AngularJS component from the ngRoute
module, $routeProvider
, into my appRouting()
function. The module is injected in the app.js when I defined the module app
. Here it is:
...
let app = angular.module('startup', ["ngRoute"]);
...
The entire source code of my "appRouting
" is like this:
export function appRouting($routeProvider) {
$routeProvider.when("/", {
templateUrl : "/assets/app/pages/index.html"
});
$routeProvider.when("/red", {
templateUrl : "/assets/app/pages/page1.html",
controller: "TestController",
controllerAs: "vm"
});
$routeProvider.when("/green", {
templateUrl : "/assets/app/pages/page2.html"
});
$routeProvider.when("/blue", {
templateUrl : "/assets/app/pages/page3.html"
});
}
Aside from how the dependency injection works, this function is pretty simple. All I did is create a function called appRouting
. It takes a parameter called $routeProvider
. This parameter looks a bit funny. And I declared it intentionally, and this is where the dependency injection happens. If I name the parameter as it is, and the dependency injection would occur correctly. If I use a different name, a null
reference exception will happen when this parameter is used for the first time.
I call the method $routeProvider.when()
to set up the sub page navigation. This method takes one object as parameter. The properties of this object determine how the sub page behaves. There are four calls, to setup four different sub pages. Three of them looks similar. All the objects contains just one property called "templateUrl
". What I do here is specify that AngularJS will display these three pages as static pages. There is the other invocation, the parameter contains three properties, "templateUrl
", "controller
", and "controllerAs
". The property "templateUrl
" specifies the template page file location. The property "controller
" specifies the name of the AngularJS controller for this page. The property "controllerAs
" specifies the alias name which the controller can be referenced on the template page. It is preferred by the latest version AngularJS to use alias to access model properties of the controller class. if you don't want to use this, then the alternative is use "$scope
" to access the model properties of the controller. This is no longer preferred.
The export
keyword is used to export the function so that other JavaScript module can import it. Now that the sub page navigation configuration is discussed, next, we will discuss the design of the TestController
.
For this application, I just need to create a very simple application with one page that associates with a controller. The controller should have a few properties that are the model properties (can be used on the page) that can be manipulated on the page. Then there will be the button which user can press and see the reaction on the page. These should sufficiently demonstrate the idea of using ES6 syntax for the design of the controller.
For this controller, I will design a simple addition calculator. There will be two input fields that user can enter integer values. Then there is another input field that will display the sum of the two entered values. If the user enters some invalid value, then the input field that displays the sum would reset to null
, then a warning will be displayed. To do the input validation and calculate the sum, the user will click the button called "Calculate".
Here is the full source code of the controller:
export class TestController {
constructor() {
this._value1 = "";
this._value2 = "";
this._sumValue = null;
}
get value1 () {
return this._value1;
}
set value1 (val) {
this._value1 = val;
}
get value2 () {
return this._value2;
}
set value2 (val) {
this._value2 = val;
}
get sumValue () {
return this._sumValue;
}
set sumValue (val) {
this._sumValue = val;
}
clickIt() {
let intVal1 = parseInt(this.value1);
let intVal2 = parseInt(this.value2);
if (isNaN(intVal1) || isNaN(intVal2)) {
alert("The integer values entered is/are invalid. Please correct.");
this.sumValue = null;
return;
}
this.sumValue = intVal1 + intVal2;
}
}
This class is very simple. It has three properties and one public
method. Let me show you what they are. First, these are the properties, they are declared and initialized in the constructor of this class.
...
constructor() {
this._value1 = "";
this._value2 = "";
this._sumValue = null;
}
...
A good way to expose these properties is through getter and setter (also referred as the accessors). These getters and setters are:
...
get value1 () {
return this._value1;
}
set value1 (val) {
this._value1 = val;
}
get value2 () {
return this._value2;
}
set value2 (val) {
this._value2 = val;
}
get sumValue () {
return this._sumValue;
}
set sumValue (val) {
this._sumValue = val;
}
...
Finally, the public
method of this class is to handle the button click event on the view. Here it is:
...
clickIt() {
let intVal1 = parseInt(this.value1);
let intVal2 = parseInt(this.value2);
if (isNaN(intVal1) || isNaN(intVal2)) {
alert("The integer values entered is/are invalid. Please correct.");
this.sumValue = null;
return;
}
this.sumValue = intVal1 + intVal2;
}
...
A closer look at this method. It first tries to parse the text field values to integers. Then it checks the value parsed, makes sure they are valid numbers. If they are not valid numbers, an alert popup would show, the sum value will be set to nothing, and no further action will be done. If the values are valid, sum of the two will be calculated and assigned to the sumValue
property. Once the value is successfully assigned, the text field for the sum would be updated instantly.
As shown, the class itself is simple. So, how can it be used by the view in this AngularJS sample application? Turned out, the use is also simple. Here is the source code of the view page:
<div class="row">
<div class="col-xs-12 col-sm-offset-2 col-sm-8 col-md-offset-3 col-md-6">
<p><input ng-model="vm.value1"> + <input ng-model="vm.value2"> =
<input ng-model="vm.sumValue" readonly> <button class="btn btn-primary"
ng-click="vm.clickIt()">Calculate</button></p>
</div>
</div>
<div class="row">
<div class="col-xs-3" style="background-color: red;">
</div>
</div>
The way these properties are referenced is the same with the way they are referenced in the old way. I have specified the controller object as "vm
". So these properties can be referenced on the view as "vm.<propertyName>
". Here it is how the properties are bound to the text fields for Value #1, Value #2 and Sum.
...
<input ng-model="vm.value1"> + <input ng-model="vm.value2"> =
<input ng-model="vm.sumValue" readonly>
...
The markup here is to set the display of value #1 plus value 2 to get the sum of the two values. And there is the button that can be clicked to calculate the sum of the two values. Once clicked, the two value will be converted into integers, then the value of the sum of these two are stored in the property sumValue
. This will cause the text field for the sum to be displayed. This button is defined as the following:
...
<button class="btn btn-primary" ng-click="vm.clickIt()">Calculate</button>
...
How to Test Sample Application
After downloading the sample source code, please unzip to a safe location. Then rename all the JavaScript files from *.sj to *.js. Then it can be compiled and packaged. Note that, since the beginning of this year, I have switched from Java 8 to Java 14. Please either use Java 14 or modify the POM file to make the project compilable with Java 8.
Use the following command to compile and package the application:
mvn clean install
Once the compiling and packaging is successful, use the following command to start up the application:
java -jar target/hanbo-angular-es6sample2-1.0.1.jar
When application successfully started up, you can use the following URL to run the application in a browser. Please use Chrome or Firefox to run this application. I believe this would also run in the WebKit based internet explorer. This was never tested. Once the page displays, you would see the same page display as in the very first screenshot. Let me post this screehshot again here:
The screenshot is showing the index sub page of this application. There are three more pages, "Red Page", "Green Page", and "Blue Page". The "Green Page", and "Blue Page" is like the index page, they are static. The page "Red Page" is the one that has the addition calculation logic. This is the screenshot of the "Red Page":
This is the screenshot of the "Green Page":
This is the screenshot of the "Blue Page":
This is it. Have fun!
Summary
Another tutorial completed. I had fun with this one. This is a simple web application. I designed this with the purpose of demonstrating the new syntax of ES6, with integration of AngularJS. For this application, I had two points of focus, one was to figure out how to setup the navigation routing, and two was to figure out how to configure and use AngularJS controller with the new syntax. After some research, both were solved without much trouble, as I have shown in this tutorial.
There are, however, more tutorials that can be derived from this tutorial. The most frequent use is factory in AngularJS, to create services objects. I would probably do a small tutorial with this. Also creating directive/component with AngularJS is also a frequent use, which can be another tutorial. I guess I can combine both into one tutorial using ES6 module, which will be available later this year. Anyways, from now to the end of this year, I will be putting out quite a few good tutorials, please check back my CodeProject profile pages for these new ones. Thank you for reading!
History
- 9th March, 2021: Initial draft