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

Made with Slides.com