Getting to Next: Using Angular 1.x with JSPM, SystemJS, and ECMAScript 6


I’ve been spending some time mulling over the best way to really leverage ECMAScript 6 (particularly the module loader) and JSPM with Angular 1.x.

Having recently taken to working with Aurelia, but staying committed to a professional life in Angular.js, this issue has become all-the-more pressing.

For those who have not used JSPM or the ES6 module loader, you are in for a treat. It is very different from the way you are probably used to working with JavaScript.

Getting Started

There are a number of great resources out there to get you up-to-speed working with these new tools. If you want to just get started with a fairly flexible scaffold, you can clone this GitHub project.

The scaffold, above, will probably work with a large number of front-end frameworks, including Sencha Touch, Angular, and Backbone. It is actually the result of a late-night attempt I made to extract the most essential aspects of the setup used in Aurelia.

It works.

You will have to spend some time, however, building with the framework of your choice on top of the scaffold. Doing so with Angular is fairly easy, once you understand the basics.

In terms of readability, Angular with ES6 is mediocre. You may start to see why the Angular 2 team went back to the drawing board. There is just too much effort spent trying to plug ES6 concepts into the way that Angular 1.x overcame the shortcomings of ECMAScript 5.1.

Let’s take a look.

Code: Building with Angular 1.x in ECMAScript 6

The index.html file is still the entry point of an Angular application in ES6. It is responsible now for doing a few main things:

  1. Defining a <ui-view> element for the main view into which Angular-UI’s uiRouter will place content.
  2. Including the SystemJS-extended module loader for ECMAScript 6 and JSPM.
  3. Including the config.js file, where JSPM automatically keeps track of front-end dependencies.
  4. Configuring SystemJS.
  5. Importing a JavaScript file (called index.js) that will bootstrap the Angular application.

index.html

<!doctype html>  
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles/styles.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <ui-view name="application"></ui-view>
  <script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
System.config({
"paths": {
"*": "dist/*.js"
}
});
</script>
<script>
System.import('index');
</script>
</body>
</html>

Next comes the Angular bootstrapper, which does a manual bootstrap of the Angular application.

index.js

import angular from 'angular';  
import 'modules/application/application-module';
angular.element(document).ready(function() {  
angular.bootstrap(document, ['Application']);
});

This file includes the angular.js dependency, as well as any Angular modules that you want to include up-front. My feeling is that it is the best practice to include a single Angular module that is the top-level Angular module and is, itself, responsible for including any other Angular module dependencies. In this case, I called this the Application module.

application-module.js

import 'angular-ui-router';  
import { ApplicationController } from './controllers/application-controller';
import tpl from 'modules/application/templates/application-template.html!'
angular.module('Application', ['ui.router'])  
.config(($stateProvider) => {
            $stateProvider.state('home', {
url: '/home',
views: {
application: {
template: tpl,
controller: ApplicationController,
controllerAs: 'controller'
}
}
});
  })
.controller('ApplicationController', ApplicationController);

This module imports the dependency on uiRouter, as well as the controller and template dependencies needed for the one state that is defined on $stateProvider in the config() block of the module.

The next part should look fairly familiar to anyone who has used Angular with uiRouter. It’s a typical Angular module definition with a config() block, using the $stateProvider dependency to define a view on /home.

What is new is that the template and controller for the state are essentially defined inline. More specifically, they are defined via the ECMAScript6 imports at the top of the file: ApplicationController and tpl are both imported, and then they are used in the state definition.

Notice the ! mark on the end of the template import line, inside the string that defines the path of the template? This tells the module loader to use the file extension to select a plugin to which to delegate the import process. (More on that later.)

At the bottom, the ApplicationController is wired in as an Angular Controller using a call to controller().

application-controller.js

export class ApplicationController {  
constructor() {
this.foo = 'bar';
}
}

The controller itself, in this example, is simple. All it does it define a fooattribute and set its value to bar. If you had any dependencies to be injected into the controller, you could pass them in as arguments on the constructor(), and then define references to them on this.

The one last thing you will need is to define a custom ES6 module loader plugin to allow loading HTML templates with import statements. This is because, by default, imported JavaScript is interpreted during import, and loading HTML would result in a syntax error.

html.js

export function instantiate(load) {  
return load.source;
}

This plugin definition tells the ES6 transpiler not to interpret the contents of an HTML file.

* * *

And with that, you should be on your way to using ECMAScript 6 and JSPM with Angular 1.x.

My impression, having gone through this, is that I am really looking forward to Aurelia, Angular 2, and other to-be-named ES6-friendly front-end frameworks more than I am looking forward to retrofitting Angular 1.x onto future JavaScript.

Nevertheless, it was an interesting exercise.

For a Gist containing the code samples, above, see this link.