Leverage 

applications

with

6

Elior Boukhobza @mallowigi
Fullstack Developer at CodeOasis

 

Elior Boukhobza

Full Stack Developer @ CodeOasis

 

 

 

 

About me

About CodeOasis

  • We are developing web applications in different stacks and technologies:
    • Javascript: Angular.JSNode.JS
    • PHP: Symfony2 - Wordpress - Drupal
    • .NET: ASP.NET - Umbraco - WPF
       
  • Large range of customers: Wix, ECI Telecom, Psagot, Superpharm, Golan Telecom...
     
  • Provide services going from outsourcing, consulting, design, product planning, hosting, storage, delivery and support.

Overview

  • The state of ECMAScript 6
  • ES6 Features overview
  • Tools and frameworks
  • The new Angular way

ES6 Support as of May 2015

  • Google Chrome/Opera: 48%
  • Mozilla Firefox: 68%
  • Internet Explorer "Edge": 72%
  • Safari (Webkit): 51%
  • Node.JS: 23%
  • IO.JS: 43%

Still not ready yet...

Source: http://kangax.github.io/compat-table/es6/

But we're getting there.

However...

We have transpilers!

Transpiler:
Transformer + Compiler
class Foo extends Bar {
  constructor () {
    super();
  }

  static method () {
    
  }
}
function Foo() {
    Bar.call(this);
}

Foo.prototype = new Bar();

Foo.prototype.method = function () {};

But some features cannot be easily transpiled

  • Modules
  • Generators
  • List comprehensions
  • Proxies
  • Decorators...

Features Overview

6

What's new in ES6 ?

  • Classes
  • Modules
  • Arrow functions (lambda functions)
  • Block scoped variables (let)
  • Constants
  • Symbols
  • Template Strings (Interpolation)
  • Shorthand object literals
  • Collections
  • Promises
  • Default Parameters
  • Spread and Rest Parameters
  • Array/Object destructuring
  • Iterators and Generators
  • Proxies and Reflect API
  • Built-in objects enhancements
  • And more...

Classes

Modules

Arrow

functions

Template Strings

Constants

Symbols

Shorthand

Object literals

Collections

Promises

Destructuring

Default/Rest/

Spread parameters

Iterators

Generators

Proxies

Reflect API

Looks cool. But can we use it now?​​

Solution:

=

Use next generation Javascript, today

Turn ES6 code into ES5

compliant code

  • Supports almost all ES6 features except for Proxies
  • Experimental support for some ES7 features
  • Full integration with most today's systems:
    • Grunt, Gulp, Broccoli, Gobble task runners
    • Backbone, Meteor, Ember plugins
    • RequireJS, CommonJS and Browserify module loaders
    • Support for Node.JS and Ruby
  • Supports sourcemaps

Turn ES6 code into ES5

compliant code

  • Supports almost all ES6 features except for Proxies
  • Experimental support for some ES7 features
  • Supports sourcemaps
  • Full integration with many task runners and compilers

Experimental support:

  • ES7 proposals: async, decorators...
  • Browser support (inside a <script> tag)
  • System.JS module loading
  • Flux support for React 

require('modules') in the browser

Browserify allows us to write CommonJS in the browser

  • No more UMD/AMD/RequireJS   awkward syntax
  • No lazy loading (nobody uses it anyway)
  • No need for "shims"
  • Browser versions of Node modules such as 'path', 'events',  'util', 'crypto'...
  • Support for ES6 "import/export" syntax
  • Works for all browsers, including IE7+ and Safari 6+
<!doctype html>
<html lang="en">
<head>
    ...
</head>
<body>
    
    <!-- build:js scripts/app.min.js -->
    <script src="scripts/config/config.js"></script>
    <script src="scripts/entities/ticket.js"></script>
    <script src="scripts/entities/ticketCollection.js"></script>
    <script src="scripts/entities/entities.js"></script>
    <script src="scripts/dashboard/ticketBuilder.js"></script>
    <script src="scripts/dashboard/consolidater.js"></script>
    <script src="scripts/dashboard/corsProxyService.js"></script>
    <script src="scripts/dashboard/controllers/appController.js"></script>
    <script src="scripts/dashboard/controllers/searchController.js"></script>
    <script src="scripts/dashboard/controllers/resultsController.js"></script>
    <script src="scripts/dashboard/controllers/resultController.js"></script>
    <script src="scripts/dashboard/dashboard.js"></script>
    <script src="scripts/app.js"></script>
    <!-- endbuild -->
</body>
</html>

Before

After

<!DOCTYPE html>
<html>
<head>
    
</head>
<body>

    <!-- vendors -->
    <script src="lib/vendors.js"></script>

    <!-- your app bundle's main -->
    <script src="js/app.js"></script>
</body>
</html>
require('angular/angular');
require('angular-ui-router/release/angular-ui-router');
require('./app-controllers');
require('./templates');

var routes = require('./routes');

module.exports = angular.module('directory', [
	'templates',
	'ui.router',
	'directory.controllers'
]).config(routes);

The main application file is the main entry point of the app

require browser libraries and modules

export stuff

import angular from 'angular/angular';
import from 'angular-ui-router/release/angular-ui-router';
import from './app-controllers';
import from './templates';

import routes from './routes';


export default angular.module('directory', [
	'templates',
	'ui.router',
	'directory.controllers'
]).config(routes);

And in ES6 syntax?

require browser libraries

export stuff

Tip: Put an "index.js" file in each of your modules directories and import them from your main app file.

What about 3rd-party libraries?

  • 3rd party libraries can be required as soon as they are in node_modules and with a package.json 
  • No more bower installnpm install is the new thing!
    (you can still use bower if you prefer using the global scope...)
  • Most popular 3rd parties libraries come with CommonJS support by default: Angular, lodash, Moment...

But what about the others? Not all libraries are in npm...

Solution: napa

napa allows installation of non-npm modules without package.json"

{
  "scripts": {
    "install": "napa"
  },
  "napa": {
    "angular": "angular/angular#v1.3.15",
    "backbone": "jashkenas/backbone#master",
    "jquery": "jquery/jquery"
  }
}

package.json :

Main entry point:

var angular = require('angular'),
    Backbone = require('backbone'),
    $ = require('jquery/dist/jquery'); 
// packages that don't have a package.json need to be specified their "main file".

Note: Requiring 3rd-party libraries also mean that they will be included in your app bundle. Make sure to minify your sources!

Plugins

  • Plugins are "transformers" that X-ify your sources
  • Examples:
    • Babelify (ES6)
    • Coffeeify (CoffeeScript)
    • Sassify (require SASS files)
    • Watchify (watch for changes and rebuild bundles)
    • Minifify (minify bundles)
    •  

ngular

design patterns

Modular structure

  • Organize your angular modules into folders
  • Each folder has an index.js that you import from outside
  • Use require/import alongside angular deps
// Add modules
import './config';
import './models';
import './services';
import './views';

import run from './run';

// The app modules
var modules = [
  // ng templates
  , 'templates'

  // app modules here
  , 'discover.config'
  , 'discover.models'
  , 'discover.services'
  , 'discover.views'

];

export default angular.module('discover', modules)
  .run(run);
// Add modules
import './config';
import './models';
import './services';
import './views';

import run from './run';

// The app modules
var modules = [
  // ng templates
  , 'templates'

  // app modules here
  , 'discover.config'
  , 'discover.models'
  , 'discover.services'
  , 'discover.views'

];

export default angular.module('discover', modules)
  .run(run);
main.js

Example module

import '../config';
import '../services';

import User from "./User.js";
import Recommendations from "./Recommendations.js";

export default angular.module('discover.models', [
  'ionic',
  'LocalStorageModule',

  'discover.config',
  'discover.services'
])
  .service('User', User)
  .service('Recommendations', Recommendations)
;

Example service

// Save deps as closures
var $http, SERVER, $localStorage;

// Every service is a class
class User {

  // Inject deps into constructor (I use angular-mocks _ hack)
  constructor (_$http_, _SERVER_, _localStorageService_) {

    // Services and constants
    $http = _$http_;
    SERVER = _SERVER_;
    $localStorage = _localStorageService_;

    // ... properties
  }

  // methods...
}

// We could also write "export default User" and use ng-annotate...
export default ['$http', 'SERVER', 'localStorageService', User];

Everything is a class

  • Use the new class syntax to declare your services/controllers/filters/directives...
  • Make use of inheritance (extends)
  • Shorthand and dynamic properties
  • Forget about the $scope!
  • Export single methods
  • Public dependencies in the instance, privates in a closure
// Still need to import MainController to extend it... which we cannot do prior to ES6!
import {MainController} from '../../MainController';

// Private deps
var $timeout, AudioPlayer;

// Export the controller without its list of deps
export class DiscoverController extends MainController {

  constructor (_$timeout_, _User_, _Recommendations_, _AudioPlayer_) {
    // Call super controller
    super();
    
    // Private deps
    $timeout = _$timeout_;
    AudioPlayer = _AudioPlayer_;

    // Public deps
    this.User = _User_;
    this.Recommendations = _Recommendations_;

    // Public properties
    this.currentSong = angular.copy(this.songs[0])
  }

  // Use ES5 getter to get a binding to "Recommendations.queue" and not a copy.
  get songs () {
    return this.Recommendations.queue;
  }

  // Shorthand syntax
  nextSong() {
    // $log is a property of MainController
    this.$log('Playing next song');

    this.isPlaying = false;
    this.Recommendations.nextSong();
  }

}

export default ['$timeout', 'User', 'Recommendations', 'AudioPlayer', DiscoverController];

Exemple with extends

import {MainController} from '../../MainController';
// Import single methods, not the whole service (bonus: alias)
import {auth as authenticate} from '../../models/User"

export class SplashCtrl extends MainController {
  constructor () {
    super();
    this.username = '';
    this.isSignUp = false;
  }

  submitForm () {
    authenticate(this.username, this.isSignUp)
      .then(() => {
        // on success change state
        super.$state.go('tab.discover');
      })
      .catch((error) => {
        this.error = error;
      })
  }
}

export default SplashCtrl;

Exemple requiring single methods

But it's probably better to stick to DI principles and inject the whole service.

Destructure isolate scope/attributes

  • Array/Object destructuring simplifies access to the isolate scope/attributes in directives
  • Allow assignment of default values to the isolate scope optional properties
  • Simplifies the use of configuration objects, such as
    <myDirective options="options">
// Destructuring scope
var {min = 0, max = 100} = $scope;

// Destructuring directive requires
function link (scope, element, attrs, ctrls) {
    var [ngModelCtrl, directiveCtrl] = ctrls;
}
function minMax () {

  return {
    restrict: 'EA',
    scope: {
      min: '@',
      max: '@'
    },
    replace: true,
    template: `<p>min is ${min}, max is ${max}</p>`,
    controller: ['$scope', function ($scope) {
      // Default destructuring values
      var {min = 0, max = 100} = $scope;
      console.log('min is %d, max is %d', min, max);
    }],
    link: function(scope, elem, attrs) {
      var {timeout, title, foo, bar} = attrs.options;
      // Do something with that
    }
  }
};

export default minMax;

No more bindings

  • Arrow functions allow to prevent the use of the ".bind" function or "context parameter" in 3rd party functions such as lodash/jquery
// Before: bind function
_.map(collection, function (item) {
  return item.property;
}).bind(this);

// Context parameter
_.map(collection, function (item) {
  return item.property;
}, this);

// Now:
_.map(collection, item => item.property);

No more arguments

  • Rest parameters prevent the use of the now deprecated arguments variable, along with the "Array.prototype.slice.call" hack.
// Before: arguments
function (arg1) {
  var myArgs = Array.prototype.slice.call(arguments, 1);
  console.log(arg1, myArgs);
}

// Now: rest params
function (arg1, args...) {
  // PS: "Array.prototype.slice.call" is now "Array.from"
  console.log(arg1, args);
}

Templates strings and filters

  • Interpolation built-in in the language
  • Multiline strings
  • Tagged strings with $filter
//Before
this.myString = 'myString is ' + upperCaseFilter(value) + '.\n';
this.myString += 'It continues on the next line with value ' + upperCaseFilter(value) + '.';

//After
this.myString = 
upperCase`myString is ${value}.
It continues on the next line with value ${value}.

Other ideas

  • Use Promise API in lieu of $q .
     
  • Protect private properties using Symbols.


     
  • Use generators/async instead of Promise chains
  • Use Block scopes instead of IIFEs

     
  • etc...
new Promise(function (resolve, reject) { ... })
var obj = {
  ...
  [Symbol('private')]: 'a private property'
}
{
  let MyController = function ($scope) { ... }
}

Summary

  • AngularJS is an awesome framework and barely needs any improvement from ES6.
  • Still, some features such as classes, arrow functions, template strings or rest parameters are a plus in the day-to-day development, while waiting for Angular 2.0
  • Whilst browser support is still not enough, transpilers are filling the gap for us, so there's no more excuses.
  • Browserify makes modularization possible and even allows the use of multiple languages such as Typescript or Coffeescript in the same application.

 

Notes

THE

FUTURE

IS

NOW

Leverage Angular 2.0 applications with ECMAScript 6

By Elior Boukhobza

Leverage Angular 2.0 applications with ECMAScript 6

  • 2,094