Leverage
applications
with
6
Elior Boukhobza @mallowigi
Fullstack Developer at CodeOasis
About me
- Fullstack Web Developer
-
Active open source contributor
- Passionate and product hunter
- Loves to teach and learn
CodeOasis
- We develop cutting-edge web applications from different technologies:
AngularJS, Node.JS, Symfony2, ASP.NET... - Large range of customers: Wix, BigPie, ECI, Super Pharm, Psagot...
- Provide services such as consulting, outsourcing, project planning, design, training, hosting and delivery.
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 () {};
Features Overview
6
What's new in ES6 ?
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:
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 starting point
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 install; npm 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!
ngular
design patterns
Modular structure
- Organize your angular modules into folders
- Each folder has an index.js that you import from outside
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)
;
// 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 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];
Example Usage
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">
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);
}
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) { ... }
}
THE
FUTURE
IS
NOW
Leverage Angular 2.0 applications with ECMAScript 6 - Short version
By Elior Boukhobza
Leverage Angular 2.0 applications with ECMAScript 6 - Short version
- 1,184