Angular Applications using ES6 Modules

Blog

September 22, 2015

TABLE OF CONTENTS

Introduction

AngularJS has been around for a few years now and is starting to see some serious traction in enterprise environments. While it’s exciting to see the enterprise move towards Angular, we’re also seeing a lot of pain from teams struggling with the unfamiliar technology for the first time. Angular’s documentation is notoriously spotty and the supporting ocean of contradictory blog posts on best practices can be a nightmare for enterprise teams trying to standardize coding style and patterns.

As if that weren’t enough, the latest version of JavaScript (ES6), approved this past summer, contains a number of new features that seem to be at odds with Angular’s architecture, specifically ES6 modules and classes.

With ES6 already certified, and the ES6 friendly Angular 2 not yet here, enterprise teams are in a tough spot right now trying to figure out best practices for future facing standards on their new Angular projects.

Fortunately, ES6 modules and classes are a really powerful technology, and can be used today to make Angular 1.x code much cleaner and more enjoyable to write. Developing Angular with ES6 modules and classes not only feels great, it also organizes your Angular 1.x code to look much more like Angular 2, making the transition much easier if your team ever wants to take the leap in the future.

Converting to ES6

So how do we do this? The first step is dealing with Angular’s built-in dependency injection system and re-factoring it to work within an ES6 module system. Angular’s dependency mechanism works by registering all modules (services, controllers, etc) on the global ‘angular’ object.

angular.module('myModule')
  .controller('MyAppComponent', function(dependency1, dependency2) {
    // My controller code using dependencies
    this.name = dependency1.getName();
  });

This mechanism let’s us register Angular components by name and then inject them into other Angular components as parameters at run-time. This was a big improvement back in 2012 when most browser applications just threw all JavaScript into a single global space. It was a huge leap forward in both code organization and testability.

Today, ES6 provides us with a standardized way of defining modules and importing them into other modules in our code, making the Angular DI system somewhat redundant. If you haven’t used ES6 modules yet, check out ES6 Module Syntax post by the ever-awesome Axel Rauschmayer. Using an ES6 module bundler like Webpack or Browserify, we can start to re-write the above Angular code into a more ES6 friendly pattern.

// myAppComponent.js
export default function MyAppComponent(dependency1, dependency2) {
    this.name = dependency1.getName();
}

// app.js
import MyAppComponent from 'myAppComponent';

export default angular
  .module('myModule', [])
  .controller('MyAppComponent', MyAppComponent);

What this does is effectively separate the re-usable application logic we care about in myAppComponent.js from the Angular DI cruft in app.js. Our application logic is now just a plain JavaScript function that can be simply instantiated by Angular, a test runner, or any other framework. This makes things significantly more readable, testable, and portable. Using the ES6 module system also gives us direct access to a world of open source modules, installed via NPM, without waiting for someone to create an ng- friendly version.

To complete our ES6 transformation and improve readability a step further, we can use ES6 classes to replace the Angular controller functions above. ES6 classes are essentially just a syntactic improvement on regular ES5 prototypical inheritance, with things like a “class” keyword, explicit inheritance, and member declaration syntax that’s more familiar to those coming over from object oriented languages like Java and C#. Again, if you haven’t gotten into ES6 classes yet, check out Axel Rauschmayer’s definitive post.

Using classes, our controller code can be re-written as follows:

// MyAppComponent.js
export default class MyAppComponent {
  constructor(dependency1, dependency2) {
    this.dependency1 = dependency1;
    this.dependency2 = dependency2;
  }

  myName() {
    return this.dependency1.getName();
  }
}

All of a sudden, our Angular 1.x controller code starts to look a lot like the example form the Angular 2 homepage:

@Component({selector: 'my-app'})
@View({template: 'Hi {{ name }}'})

// Component controller
class MyAppComponent {
  constructor() {
    this.name = 'Ali';
  }
}

This will make our lives a lot easier if we ever choose to convert this code base to Angular2, and a lot more maintainable years down the line when Angular 2 is the norm. (One thing to note is that you can’t use classes to define directives. For those, stick with the original function syntax)

Unit Testing

We mentioned earlier that organizing your Angular using ES6 modules makes it more testable, so let’s take a look at unit testing in action. Standard unit testing in Angular can actually get really tedious and confusing because instantiating an Angular component (controller/directive) in a test requires initializing the Angular Dependency injection system and using the uncomfortably magical Angular provider API to provide dependencies. E.g. –

beforeEach(module('app'));

beforeEach(module(function($provide) {
  var TodoServiceMock = {...};
  $provide.value('TodoService', TodoServiceMock);
}));

beforeEach(inject(function(_$rootScope_, _$controller_, _TodoService_){
  $controller = _$controller_;
  $rootScope = _$rootScope_;
  scope = $rootScope.$new();

  $controller('TodoController', {
    $scope: scope
    TodoService: _TodoService_
  });
}));

// Test goes here

As you can see, there’s lots of Angular API specific boilerplate in there that we’d rather not deal with.

The ES6 module component pattern we’ve outlined makes unit testing much easier because it isolates the logic you want to test in a plain JavaScript function. Simply instantiate your component, and test it like any other piece of vanilla JavaScript.

import MyComponent from './my-component';

let component;  
describe('myComponent', function() {
  beforeEach(function() {
    component = new MyComponent();
  });

  it('should have a name', function () {
    assert.exists(component.name);
  });
});

Testing your application logic in this way is super straight forward. If you need to mock dependencies, just import them from another module and pass them in to the component constructor.

For more details on unit testing with this pattern, check out Tomas Trajan’s post on testing Angular with ES6.

Conclusion

If your team is new to Angular, or is just preparing for a new project, consider using this ES6 module pattern to future-proof your code and prevent a possibly large re-write down the line. This pattern gives developers access to all the ES6 goodies we’ve been reading about, while still working within the bounds of the popular Angular framework.

For a complete example of how ES6 and Angular can come together in a project, check out our example ES6 Angular with ES6 project. It includes examples of controllers, directives, and unit testing.

Authored By

Assaf Weinberg

Assaf Weinberg

Chuck Bridgen, Client Solutions Principal

Chuck Bridgen

Client Solutions Principal

Meet our Experts

Assaf Weinberg

Assaf Weinberg

Chuck Bridgen, Client Solutions Principal

Chuck Bridgen

Client Solutions Principal

Let's chat.

You're doing big things, and big things come with big challenges. We're here to help.

Read the Blog

By clicking the button below you agree to our Terms of Service and Privacy Policy.

levvel mark white

Let's improve the world together.

© Levvel & Endava 2023