AngularJS and ES2015+

Berlin Angular Meetup #26

by Egor Smirnov / @egorsmirnovme

What is ES2015?

  • Current JavaScript Language Standard
  • Approved in June 2015
  • ES2015 == ECMAScript 2015 == ES6
  • Backward-compatible with ES5

How ES2015 relates to Angular?

  • Improves your Developer Experience, not the user experience
  • Future-proof
  • Good introduction to TypeScript (recommended for starting Angular2 applications); TypeScript is the superset of ES2015

Let's start with ES2015 Features

1. Let + Const

Let has a block scope

						
var apples = 5;
if (true) {
  var apples = 10;
  console.log(apples); // 10
}
console.log(apples); // 10 
						
					
						
let apples = 5;
if (true) {
  let apples = 10;
  console.log(apples); // 10
}
console.log(apples); // 5
						
					

Visible only after declaration

						
console.log(a); // undefined
var a = 5;
						
					
						
alert(a); // Uncaught SyntaxError: ...
let a = 5;
						
					

Const

						
const LANGUAGE = 'DE';
LANGUAGE = 'EN'; 
// Uncaught TypeError: Identifier 'LANGUAGE' has already been declared(…)
						
					

2. Template Strings

Template Strings

						
let apples = 2;
let oranges = 3;
const template = `
  
${apples} + ${oranges} = ${apples + oranges}
`; function myDirective() { return { template, controller: DirectiveController, controllerAs: 'ctrl', restrict: 'E', bindToController: true, scope: {} }; }

3. Modules

Modules

  • As soon as we have a complex project we decide to split it into multiple modules.
  • JavaScript already has various module loading standards - AMD / UMD / CommonJS.
  • ES2015 dictates new standard for module loading.
  • The difference is that now it is official standard.
  • Still no native browser support, have to use SystemJS / webpack etc. to bundle modules.

Modules & Angular 1.x

						npm install --save angular angular-ui-router
					

Modules & Angular 1.x

routes.js

						
/*@ngInject*/
function routes($stateProvider) {
// state definitions here
}
export default angular.module('app.routes')
  .config(routes);
						
					

config.js

						
const configObject = {baseUrl: 'http://some-url.com'};
export default angular.module('app.config')
  .value(configObject);
						
					

Modules & Angular 1.x

main.js

						
import angular from 'angular';
import angularUiRouter from 'angular-ui-router';
import routes from './routes';
import config from './config';

export default angular.module('app', [
  angularUiRouter,	
  config.name,
  routes.name
]); 
						
					

Modules

numbers.js

						
export let one = 1;
export let two = 2;
export let three = 3;							
						
					

someFile.js

						
import {one, two as numberTwo} from "./numbers";
console.log(`${one} and ${numberTwo}`); // prints '1 and 2'
						
					

someOtherFile.js

						
import * as numbers from "./numbers";
console.log(`${numbers.one} and ${numbers.two}`); // prints '1 and 2'
						
					

4. Classes

Classes

Controller classes

						
class MyController {
  /* @ngInject */
  constructor(userService) {
    this.fullName = userService.getFullName();
  }
}
export MyController;
angular.module('app').controller('MyController', MyController);
						
					
						
import { MyController } from './MyController';
angular.module('app', []).controller('MyController', MyController);
							
// or use this controller as directive controller somewhere

angular.module('app').directive('MyDirective', function() {
  return {
    controller: MyController,
    controllerAs: 'vm',
    ...
  };
});

						
					

Service classes

						
class UserService {
  /* @ngInject */
  constructor($http) {
    this.$http = $http;
    this.firstName = 'John';						
    this.lastName = 'Doe';
  }
  // getter
  get fullName() {
   return `${this.firstName} ${this.lastName}`;
  }
  // setter
  set fullName(newValue) {
    [this.firstName, this.lastName] = newValue.split(' ');
  }
}
						
					

Classes

						
class MyService extends BaseService {
  constructor(initialData) {
    super(initialData);
    //...
  }
  updateData() {
    //...
    super.update();
  }
  static defaultData() {
    // usable by calling MyService.defaultData()
    return 'no data available';
  }
}
						
					

Classes

5. Arrow Functions

Arrow Functions

						
class UserService {
  /* @ngInject */
  constructor($http) {
    this.$http = $http;
    this.users = [];
  }
  fetchUsers() {
    return this.$http
               .get('http://super-cool-api.com/users')
               .then(response => {
                  // arrows share the same lexical this 
                  //as their surrounding code			
                  this.users = response.data.users;
               });
    }
}
						
					

6. Destructuring

Array Destructuring

						
let arr = ['John', 'Doe'];
let [firstName, lastName, country = 'Germany'] = arr;
							
console.log(firstName); // John
console.log(lastName);  // Doe
console.log(country);  // Germany
						
					

Array + Spread operator

						
let arr = [1, 2, 3, 4];
[a, ...rest] = arr;
							
console.log(a); // 1
console.log(rest);  // [2, 3, 4]
						
					

Object Destructuring

						
let obj = {
  firstName: 'John',
  lastName: 'Doe'	
};
const {firstName, lastName, country = 'Germany'} = obj;

console.log(firstName); // John
console.log(lastName);  // Doe
console.log(country);  // Germany
						
					

Destructuring and Angular 1.x

						
angular.directive('myDirective', function () {
  return {
    controller: myController,
    require: ['ngModel', 'other'],
    link: function(scope, element, attrs, controllers) {
      // destructuring in action
      const [ngModelCtrl, otherCtrl] = controllers;
      // ..							
    }
  };
});
						
					

Destructuring and Angular 1.x

						
angular.directive('myDirective', function () {
  return {
    controller: myController,
    require: ['ngModel', 'other'],
    // perform destructuring in function declaration
    link: function(scope, element, attrs, [ngModelCtrl, otherCtrl]) {
      //..
    }
  };
});
						
					

7. Promises

We now have official API for promises :)

						
function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve('some value'), 2000);
  });
}

timeout().then((value) => console.log(value));
						
					

Angular 1.3 introduces $q API similar to official spec

						
function myFunctionThatReturnsAPromise() {
  return $q(function (resolve, reject) {
    anAsyncFunction(function (success) {
      resolve(success);
    }, function (error) {
      reject(error);
    });
  });
}

myFunctionThatReturnsAPromise().then(resolveFn, rejectFn);
						
					

8. Map + Set / WeakMap + WeakSet

Map / Set

						
// Map is a collection for storing values with arbitrary keys
var map = new Map();
map.set(2, 'value1');
map.set(true, 'value2');
// keys could be objects as well
console.log(map.get("2")); // value1
console.log(map.get(true)); // value2

// Set is a collection for storing unique values
var set = new Set();
set.add('value1').add('value2').add('value1');
assert(set.size === 2);
assert(set.has('value2') === true);
							
// Map / Set additionally have iteration methods - keys(), values(), forEach()							
						
					

WeakMap / WeakSet

Same as Map / Set, but leak-free. Allows the garbage collector to delete its elements. If some object is stored only in WeakMap / WeakSet, then it's deleted from memory.

						
let users = [
  {name: 'Peter'},
  {name: 'John'},
  {name: 'Kate'}
];
let weakMap = new WeakMap();

weakMap.set(users[0], 'some info 1');
weakMap.set(users[1], 'some info 2');
weakMap.set(users[2], 'some info 3');

users.splice(0, 1); // we deleted 'Peter' from users
console.log(weakMap.get(users[0])); // this is now 'some info 2'
						
					

WeakMap and Angular

Could be considered as private class variables. Additional benefit is memory management for free.

						
var _state = new WeakMap();
var _accountService = new WeakMap();

class SigninController {
  constructor($state, accountService) {
    _state.set(this, $state);
    _accountService.set(this, accountService);
  }
  login() {
    _accountService.get(this).login().then(()=> {
      _state.get(this).go('somewhere');
    });
  };
}
						
					

9. Generators

Generators

  • New type of JavaScript functions.
  • Generator are able to stop their execution flow at arbitrary moments of time and return intermediate result. Later generator's flow could be resumed.
  • Convenient for generating and iterating over sequences.
  • Additional use-case is controlling of asynchronous flows.

Generators

						
function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();
console.log(generator.next());  // Object {value: 1, done: false}
console.log(generator.next());  // Object {value: 2, done: false}
console.log(generator.next());  // Object {value: 3, done: true}
						
					

Generators + async + co.js

						
let $http = require('request-promise-json');
let co = require('co');

function *bitcoinRate() {
  let r = yield $http.get('http://api.bitcoinaverage.com/ticker/USD');
  // next line won't be executed until HTTP request is completed
  console.log('1 Bitcoin == ' + r.last + ' USD');
}

co(bitcoinRate);
						
					

Generators + Protractor

						
var co = require('co');
var cats = require('./cats.json');
var EC = protractor.ExpectedConditions;

beforeEach(co.wrap(function* () {
  yield http.put(DB_URL + '.json', cats);
  yield browser.get('/index.html');

  // wait for the for the model to be loaded (for the cats to be ready)
  var mainElement = element(by.className('topcat-container'));
  yield browser.wait(EC.presenceOf(mainElement));
  yield browser.wait(function () {
    return mainElement.evaluate('vm.ready');
  });
}));
						
					

Generators + Protractor

ES2016

ES2016

  • Not part of the current ECMAScript standard.
  • Some of these features might become a standard in 2016.
  • You could use them now as an experimental features. But be careful, they might be thrown away from the standard in the future.

Async / Await

Async / Await

						
async function doAsyncOp () {
  var val = await asynchronousOperation();
  // this line will be executed only after previous one is completed						
  return val;
};

async function main() {
  var val = await doAsyncOp();
  // this line will be executed only after previous one is completed
  console.log(val);
}
							
main();
							
					

Async / Await and $http

If you return Promise from function then it could be used in await block.

						
async function doRequest() {
  var value = await $http('http://awesome-website.com/api');
  console.log(`Wow, we have this value - ${value}`);
}
						
					

Async / Await and Mocha tests

						
function doRequest(number) {
  return new Promise((resolve, reject) => {
	doSomeAsyncOperation(number).then((value) => resolve(value));
  });
}
it('should check that everything is ok', async () => {
  let response = await doRequest(1000);
  expect(variable).toEqual(response);
});
						
					

Class Instance Fields / Static Properties

Class Instance Fields / Static Properties

						
class MyService() {
  myProp = 12;
  static myStaticProp = 34;
							
  constructor() {
    console.log(this.myProp); // Prints '12'
    console.log(MyService.myStaticProp); // Prints '34'
  }
}
						
					

Decorators

Decorators

“Decorators make it possible to annotate and modify classes and properties at design time.”
https://github.com/wycats/javascript-decorators

Decorators could be used for a lot of fancy stuff including reducing amount of boilerplate code.

Decorators and Angular

	
						
@Inject('$http', 'UserService')
class Account {
  constructor($http, UserService) {
    this.$http = $http;
    this.UserService = UserService;
  }
}
						
					
						
						
function Inject(...dependencies) {
  return function decorator(target, key, descriptor) {
    // target is our class constructor
    target.$inject = dependencies;
  };
}
						
					

Decorators and Angular

How to use ES2015+ today?

Some notes about testing

  • Whenever possible use and test pure ES2015 modules instead of Angular modules.
  • For code coverage report use isparta instead of istanbul. It supports ES2015.
  • For code coverage with Karma use isparta instrumenter. More info about setup is available here

Angular2

Angular2

  • You are able to write code in ES5 or ES2015 or TypeScript.
  • Best developer experience could be achieved by using TypeScript.
  • In case you decide to use TypeScript the time you've invested into the learning of ES2015 won't be waste. TypeScript includes a lot of features of ES2015 and some additional ones.

Angular2 APP code excerpt

						
						
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
class Hero {
  id: number;
  name: string;
}
@Component({
  selector: 'my-app',
  template:`.. some template ..`
})
class AppComponent {
  public title = 'Tour of Heroes';
  public hero: Hero = {
   id: 1,
   name: 'Windstorm'
  };
}
bootstrap(AppComponent);
						
					

TypeScript includes

  • A lot of ES2015 / ES2016 (or ES2015 / ES2016-like) features.
  • Type annotations, type inference and compile-time type checking. Beneficial for complex applications.
  • Interfaces / Enumerated types / Generics.

Thank you!

Links Part 1

- http://kangax.github.io/compat-table/es6/
- http://kangax.github.io/compat-table/es7/
- https://babeljs.io/docs/learn-es2015/
- http://habrahabr.ru/post/257305/
- http://www.michaelbromley.co.uk/blog/350/exploring-es6-classes-in-angularjs-1-x
- http://blog.thoughtram.io/angularjs/es6/2015/...html
- https://gist.github.com/stryju/545c67519dbf89014686
- http://martinmicunda.com/2015/07/13/how-to-use-ES2016-decorators-to-avoid-angular-1x-boilerplate-code/
- http://martinmicunda.com/2015/10/19/how-to-start-writing-unit-tests-with-es6-angular-1x-and-jspm/

Links Part 2

- https://medium.com/@tomastrajan/proper-testing-of-angular-js-applications-with-es6-modules-8cf31113873f
- http://www.sitepoint.com/simplifying-asynchronous-coding-es7-async-functions/
- https://github.com/ngUpgraders/ng-forward
- https://github.com/MikeRyan52/angular-decorators
- https://github.com/wycats/javascript-decorators
- https://github.com/jeffmo/es-class-static-properties-and-fields