Writing AngularJS Apps Using ES6

Maxim Dzhuliy
02-19 15:30
  1. JavaScript
  2. Web Development

Writing AngularJS Apps Using ES6
As many of you are aware, ECMAScript 6 is in its draft state now and is expected to be finalized some time this year. But it already caught a lot of attention in the community and browsers have already started implementing it. We also have a number of transpilers like Traceur, 6to5, and many that convert ES6 code to ES5 compatible code.

It is possible to write any piece of JavaScript that we are writing everyday using ES6. To do this, we need to be aware of the key features of ES6 and know which piece fits where. In this article, we will see how can we use features of ES6 to build different pieces of an AngularJS application and load them using ES6 modules to make them work on a page. The sample code contains a simple online book shelf application. We will see how it is structured and written.

A Note on the BookShelf application

The sample BookShelf application contains following views:

  1. Home page: Shows a list of active books and books can be marked as read and moved to archive from this page
  2. Add book page: Adds a new book to the shelf by accepting title of the book and name of author. It doesn’t allow a duplicate title
  3. Archive page: Lists all archived books

Setting up Application for ES6

As we will be using ES6 to write the front-end part of the application, we need a transpiler to make the ES6 features understandable for all the browsers. We will be using traceur client side library to compile ES6 script on the fly and run it on the browser. This library is available on bower. Sample code has an entry for this library in bower.json.

In the home page of the application, we need to add a reference to this library and add the following script to it:


traceur.options.experimental = true;
new traceur.WebPageTranscoder(document.location.href).run();


JavaScript code of the application is divided into multiple files. These files are loaded into the main file using ES6 module loader. As today’s browsers can’t understand ES6 modules, Traceur polyfills this feature for us.

In the sample code, the Bootstrap.js file is responsible for loading the main AngularJS module and manually bootstrap the Angular app. We cannot use ng-app to bootstrap the application as modules are loaded asynchronously. Following is the script in this file:


import { default as bookShelfModule} from './ES6/bookShelf.main';
angular.bootstrap(document, [bookShelfModule]);


Here, bookShelfModule is name of the AngularJS module containing all the pieces. We will see content of the bookShelf.main.js file later. The bootstrap.js file is loaded in index.html file using the following script tag:


<script type="module" data-after="ES6/bootstrap.js"></script>


Defining Controllers

AngularJS controllers can be defined in two ways:

  1. Controllers using $scope
  2. Using “controller as” syntax

Second approach fits better with ES6, as we can define a class and register it as a controller. The properties associated with an instance of the class would be visible through alias of the controller. In addition, the “controller as” syntax is comparatively less coupled with $scope. If you are not aware, $scope would be removed from the framework in Angular 2. So, we can train our brains to be less dependent on $scope from now on by using the controller as syntax.

Though classes in ES6 keep us away from the difficulty of dealing with prototypes, they don’t support a direct way for creating private fields. There are some indirect ways to create private fields in ES6. One of them is to store the values using variables at the module level and not including them in export object.

We will use WeakMap to store the private fields. Reason behind choosing WeakMap is, the entries of WeakMap that have objects as keys are removed once the object is garbage collected.

As stated above, home page of the application loads and displays list of active books. It depends on a service to fetch data and to mark a book as read or move to archive. We will create this service in the next section. To make the dependencies injected into controller’s constructor available in instance methods, we need to store them in the WeakMaps. For controller of home page, we have two dependencies: service performing AJAX operations and $timeout (used to show success messages and hide them after certain time). We also need a private init method to fetch all active books as soon as controller loads. So, we need three WeakMaps. Let’s declare the WeakMaps as constants to prevent any accidental re-assignment.

Following snippet creates these WeakMaps and the class for HomeController:


const INIT = new WeakMap();
const SERVICE = new WeakMap();
const TIMEOUT = new WeakMap();
class HomeController{
  constructor($timeout, bookShelfSvc){
    SERVICE.set(this, bookShelfSvc);
    TIMEOUT.set(this, $timeout);
    INIT.set(this, () => {
      SERVICE.get(this).getActiveBooks().then(books => {
        this.books = books;
  markBookAsRead(bookId, isBookRead){
    return SERVICE.get(this).markBookRead(bookId, isBookRead)
      .then(() => {
        this.readSuccess = true;
        this.readSuccessMessage = isBookRead ? "Book marked as read." : "Book marked as unread.";
        TIMEOUT.get(this)(() => {
          this.readSuccess = false;
        }, 2500);
    return SERVICE.get(this).addToArchive(bookId)
      .then(() => {
        this.archiveSuccess = true;
        TIMEOUT.get(this)(() => {
          this.archiveSuccess = false;
        }, 2500);


The above snippet uses following features of ES6:

  1. Class and WeakMaps, as already mentioned
  2. Arrow function syntax to register callbacks. this reference inside the arrow functions is same as this reference outside. Which is current object of the class
  3. New syntax for creating a method and attaching it to object without using the function keyword

Let’s apply dependency injection and register this class as a controller:


HomeController.$inject = ['$timeout', 'bookShelfSvc'];
export default HomeController;


As you see, there is no difference in the way the we applied dependency injection; it is same as the way we do in ES5. We are exporting the HomeController class from this module.

Check the code of AddBookController and ArchiveController. They follow similar structure. The file bookShelf.controllers.js imports these controllers and registers them to a module. Following is the code in this file:


import HomeController from './HomeController';
import AddBookController from './AddBookController';
import ArchiveController from './ArchiveController';
var moduleName='bookShelf.controllers';
angular.module(moduleName, [])
  .controller('bookShelf.homeController', HomeController)
  .controller('bookShelf.addBookController', AddBookController)
  .controller('bookShelf.archiveController', ArchiveController);
export default moduleName;


The bookShelf.controllers module exports name of the AngularJS module it crated, so that the name can be imported in another module to create to create the main module.

Defining Services

Service is an overloaded term in general and Angular as well! The three types of services used are: Providers, Services and Factories. Out of them, Providers and Services are created as instances of types, so we can create classes for them. Factories are functions that return objects. I can think of two approaches for creating a factory:

  1. Same as in ES5, create a function returning an object
  2. A class with a static method returning an instance of the same class. This class would contain the fields that have to be exposed from the factory object

Let’s use the second approach to define a factory. This factory is responsible to interact with the Express API and serve data to the controllers. The factory depends on $http service of Angular to perform AJAX operations. As it has to be a private field in the class, we will define a WeakMap for it.

Following snippet creates the factory class and registers the static method as a factory:


var moduleName='bookShelf.services';
const HTTP = new WeakMap();
class BookShelfService
    HTTP.set(this, $http);
    return HTTP.get(this).get('/api/activeBooks').then(result => result.data );
    return HTTP.get(this).get('/api/archivedBooks').then(result => result.data );
  markBookRead(bookId, isBookRead){
    return HTTP.get(this).put(`/api/markRead/${bookId}`, {bookId: bookId, read: isBookRead});
    return HTTP.get(this).put(`/api/addToArchive/${bookId}`,{});
    return HTTP.get(this).get(`/api/bookExists/${title}`).then(result =>  result.data );
    return HTTP.get(this).post('/api/books', book);
  static bookShelfFactory($http){
    return new BookShelfService($http);
BookShelfService.bookShelfFactory.$inject = ['$http'];
angular.module(moduleName, [])
  .factory('bookShelfSvc', BookShelfService.bookShelfFactory);
export default moduleName;


This snippet uses the following additional features of ES6 apart from class and arrow functions:

  1. Static member in the class
  2. String templates to concatenate values of variables into strings

Defining Directives

Defining a directive is similar to defining a factory, with one exception. The exception is, we have to make an instance of the directive available for later use inside the link function. Because, the link function is not called in the context of the directive object. So, this reference inside the link function is not same as the directive object. We can make the object available through a static field.

We will be creating an attribute directive that validates title of book entered in the textbox. It has to call an API to check if the title already exists and invalidate the field if the title is found. For this task, it needs the service we created in the previous section and $q for promise.

Following snippet creates a directive and registers with a module.


var moduleName='bookShelf.directives';
const Q = new WeakMap();
const SERVICE = new WeakMap();
class UniqueBookTitle
  constructor($q, bookShelfSvc){
    this.require='ngModel';  //Properties of DDO have to be attached to the instance through this reference
    Q.set(this, $q);
    SERVICE.set(this, bookShelfSvc);
  link(scope, elem, attrs, ngModelController){
    ngModelController.$asyncValidators.uniqueBookTitle = function(value){
      return Q.get(UniqueBookTitle.instance)((resolve, reject) => {
        SERVICE.get(UniqueBookTitle.instance).checkIfBookExists(value).then( result => {
  static directiveFactory($q, bookShelfSvc){
    UniqueBookTitle.instance =new UniqueBookTitle($q, bookShelfSvc);
    return UniqueBookTitle.instance;
UniqueBookTitle.directiveFactory.$inject = ['$q', 'bookShelfSvc'];
angular.module(moduleName, [])
  .directive('uniqueBookTitle', UniqueBookTitle.directiveFactory);
export default moduleName;


Here, we could have used promise API of ES6, but that would involve calling $rootScope.$apply after the promise produces a result. Good thing is, promise API in AngularJS 1.3 supports a syntax similar to the ES6 promises.

Defining Main Module and Config block

Now that we have modules of directives, controllers and services ready, let’s load them in one file and create the main module of the application. As first thing, let’s import the modules.


import { default as controllersModuleName } from './bookShelf.controllers';
import { default as servicesModuleName } from './bookShelf.services';
import { default as directivesModuleName } from './bookShelf.directives';


Config block would define routes for the application. This can be a simple function as it doesn’t have to return any value.


function config($routeProvider){
    .when('/archive', {
config.$inject = ['$routeProvider'];


Finally, let’s define the main module and export its name. If you remember, this name is used in the bootstrap.js file for manual bootstrapping.


var moduleName = 'bookShelf';
var app = angular.module(moduleName, ['ngRoute','ngMessages', servicesModuleName, controllersModuleName, directivesModuleName])
export default moduleName;



I think, by now you are familiar with using ES6 to write AngularJS apps. AngularJS 2.0 is being written completely using ES6. Being web developers, today we need to be aware of the way we have to write our code in near future and ES6 truly solves many problems that used to bug all JavaScript programmers for years. Using it with AngularJS is a lot of fun!

Feel free to express your opinions or ask your questions in comments. I will try my best.

Sample code of this article can be found on this GitHub repository.
Original source