es2015 - enhance angular 1x applications
TRANSCRIPT
AGENDAWhat?Why?How?Raw meat!
WHAT?ES2015 is a significant and the first update to the
language since ES5 was standardized in 2009
FEATURESlet + const arrows classes
enhanced object literals modules promises
template strings destructuring default + rest + spread
iterators + for..of generators subclassable built-ins
map + set + weakmap + weakset proxies symbols
math + number + string + array + object APIs binary and octal literals tail calls
unicode
WHY?This Ecma Standard has been adopted by the General
Assembly of June 2015.
PROSBackward compatibilityLess boilerplateBetter maintainabilityBetter preparation for Angular 2.0Easier for newcomersStaying current
Slide from Jafar Husain's talk: ES7 evolution of JS
CONSBrowser compatibilityRequire transpilationNo established best practisesNew things to learn
http://kangax.github.io/compat-table/es6
SIMPLE INTEGRATIONvar gulp = require("gulp");var babel = require("gulpbabel");
gulp.task("default", function () return gulp.src("src/app.js") .pipe(babel()) .pipe(gulp.dest("dist")););
JUMP START WEBPACK + BABEL +ANGULAR: GULP-ANGULAR
yo gulpangular
DIVE DEEP
LET, CONST, BLOCK SCOPElet a = 2; let a = 3; console.log( a ); // 3console.log( a ); // 2
const a = 2; console.log( a ); // 2 a = 3; // TypeError!
BLOCK SCOPING ES5 VS ES2015if (true) function weather() console.log( "Sun" ); else function weather() console.log( "Rain" ); weather();
ES5var funcs = [];for (var i = 0; i < 5; i++) funcs.push( function() console.log( i ); );funcs[3](); // 5
ES2015var funcs = [];for (let i = 0; i < 5; i++) funcs.push( function() console.log( i ); );funcs[3](); // 3
TEMPORAL DEAD ZONE(TDZ)
TEMPORAL DEAD ZONE (TDZ)console.log( a ); // undefinedconsole.log( b ); // ReferenceError!var a;let b;
if( typeof a === 'undefined' ) console.log('a is ok');
if( typeof b === 'undefined' ) //ReferenceError! console.log('b is ok');
let b;
*it's not an issue when using babel
(NOT SO) CONSTconst arr = ['dog', 'cat', 'snake'];arr.push('fish');console.log(arr); // ['dog', 'cat', 'snake', 'fish'];
EVOLUTION OF CONTROLLERS
BEGINING'use strict';
angular.module('app').controller('TodoCtrl', function($scope) $scope.todos = [];
$scope.newTodo = '' ;
$scope.addTodo = function(todo) $scope.todos.push(todo);
$scope.removeTodo = function (todo) $scope.todos.splice($scope.todos.indexOf(todo), 1); ;);
<div ngcontroller="TodoCtrl"> <input type="text" ngmodel="newTodo"> <button type="button" ngclick="addTodo(newTodo)"></button> <ul> <li ngrepeat="todo in todos"> todo <button type="button" ngclick="removeTodo(todo)"></button> </li> </ul></div>
SMALL IMPROVEMENTS(function () 'use strict'; angular.module('app').controller('TodoCtrl', TodoCtrl);
function TodoCtrl($scope) $scope.todos = []; $scope.newTodo = '' ;
$scope.addTodo = function addTodo(todo) $scope.todos.push(todo);
$scope.removeTodo = function removeTodo(todo) $scope.todos.splice($scope.todos.indexOf(todo), 1); ; );
controllerAs<div ngcontroller="TodoCtrl as ctrl"> <input type="text" ngmodel="ctrl.newTodo"> <button type="button" ngclick="ctrl.addTodo(newTodo)"></button> <ul> <li ngrepeat="todo in ctrl.todos"> todo <button type="button" ngclick="ctrl.removeTodo(todo)"></button </li> </ul></div>
(function () 'use strict'; angular.module('app').controller('TodoCtrl', TodoCtrl);
function TodoCtrl() this.todos = []; this.newTodo = '' ;
this.addTodo = function addTodo(todo) this.todos.push(todo);
this.removeTodo = function removeTodo(todo) this.todos.splice(this.todos.indexOf(todo), 1); ; );
ALMOST THERE BUT ..
FINAL FORM(function () 'use strict'; angular.module('app').controller('TodoCtrl', TodoCtrl);
function TodoCtrl() var vm = this; vm.todos = []; vm.newTodo = '' ;
vm.addTodo = addTodo; vm.removeTodo = removeTodo;
function addTodo(todo) vm.todos.push(todo);
function removeTodo(todo) vm.todos.splice(vm.todos.indexOf(todo), 1);
ES6 CLASSES'use strict';
class TodoCtrl constructor() this.todos = []; this.newTodo = '';
addTodo(todo) this.todos.push(todo);
removeTodo(todo) this.todos.splice(this.todos.indexOf(todo), 1); ;angular.module('app').controller('TodoCtrl', TodoCtrl);
ORLY?
ES5 PROTOTYPESfunction TodoCtrl() this.todos = []; this.newTodo = '';TodoCtrl.prototype.addTodo = function(todo) this.todos.push(todo);TodoCtrl.prototype.removeTodo = function(todo) this.todos.splice(this.todos.indexOf(todo), 1);;angular.module('app').controller('TodoCtrl', TodoCtrl);
SETTERS AND GETTERSclass MyClass constructor() this._prop = 0; get prop() console.log('getter'); return this._prop; set prop(value) console.log('setter: ' + value); this._prop = value;
let inst = new MyClass();inst = 123; // setter + 123inst.prop; // getter //123
EASY EXTENDINGclass Foo constructor(a,b) this.x = a; this.y = b;
gimmeXY() return this.x * this.y;
class Bar extends Foo constructor(a,b,c) super( a, b ); this.z = c;
gimmeXYZ()
CLASS SUMMARYMust be used with new operator, unlike ES5Foo.call(obj);It's not hoisted!Can not declare properties outside of constructor(except ES7 private proposal)Can not access super properties in constructorConstructor is optional
HANDLING DIclass Ctrl constructor(Service, AnotherService) this.Service = Service; this.AnotherService = AnotherService;
doSomething() this.Service.doSomething();
MY PREFERRED WAYlet internal;
class Ctrl constructor(Service, AnotherService) internal = Service, AnotherService;
doSomething() internal.Service.doSomething();
BE CAREFUL WITH GETTERS<span ngrepeat="n in ctrl.items100 track by $index"> e2Ctrl.test</span>
class Ctrl constructor() this.items100 = new Array(100); this._test = 'some test value';
get test() console.log('test'); return this._test;
//100x test + in digest cycles
COMMON LOGIC IN SERVICESclass StateAware constructor() this.state = current: 'init' ;
load() this.state.current = 'loading'; return this.loadData() .then((resp) => this.data = resp; ) .finally(() = > this.state.current = 'ready'; );
HOW ABOUT DIRECTIVES?
class Sample constructor() this.restrict = 'E'; this.scope = ; this.controller = 'SampleCtrl'; this.controllerAs = 'ctrl'; this.templateUrl = 'sample.html'; this.bindToController = info: '=' ; angular.module('app').directive('sample', () => new Sample());
MODULES
KEEPING IT SIMPLEconsole.log('This will fire on import');var globalVariable = "This looks bad";
class Ctrl something() return 'something'; angular.module('app').controller('Ctrl', Ctrl);
angular.module('app', []);import 'Ctrl';console.log(globalVariable);
//This will fire on import//undefined
BEING MORE SPECIFICclass Service something() return 'something'; export default Service;
import Service from './services/Service';
angular.module('app', []).service('Service', Service);
BEING PICKYclass Component constructor() //.. standard DDO this.controller = 'Ctrl'; this.controllerAs = 'ctrl'; this.bindToController = true; this.template = '<div>ctrl.something</div>'; class Ctrl constructor() this.something = 'Some text'; export Component, Ctrl ;
import Component, Ctrl from 'componentDirective';angular.module('app', []) .controller('Ctrl', Ctrl) .directive('component', () => new Component());
IMPORTING WITH ALIASexport function helperFunction() // ..export function getSomething() // ..export var randomVariable = 'o_O';
import * as lib from 'helpers';
lib.helperFunction();console.log(lib.randomVariable);
WHY MODULES?Standarized syntaxBetter code organizationAvoid pollution of global namespace
let data = [ id: 1, name: 'John Snow', deleted: true, id: 2, name: 'Ned Stark', deleted: true, //.. far far down .. id: 60, name: 'Aria Stark', deleted: false];
Standard functiondata.filter(function(person) return !person.deleted;);
Arrow functiondata.filter(person => !person.deleted);
USAGE IN CALLBACKSfunction Clickable() this.clicks = 0;
$('.elem').on('click', () => this.clicks++; console.log(this.clicks); );
MULTIPLE ARGUMENT CALLSdata.forEach((person, index) => console.log(`$index. $person.name`););
SYNTAX GOTCHAlet returnItem = id => (id: id, name: 'Some name');
SUMMARY1. Lexical this2. Can't change this3. Can't be used with new4. Don't have arguments array-like object
TEMPLATE STRINGSlet name = "Ice Cube";let greeting = `Hello $name!`;console.log(greeting); //Hello Ice Cube
var text =`This ismore then oneline!`;
console.log( text );//This is//more then one//line!
TAGGINGlet message = Currency`You should pay $value @currency@ discount.`;
function Currency(templateData, ...args) return templateData.map((part, index) => let str = part; if( args[index] ) str = part + String(args[index]); return str.replace('@currency@', 'PLN'); ).join('');
setOptions(0, 500); //500, 500, blur
DEFAULT, SPREAD, RESTES5
function setOptions(debounce, interval, event) debounce = debounce || 500; interval = interval || 100; event = event || 'blur';
console.info(debounce, interval, event);
setOptions(); //500, 100, blursetOptions(200); //200, 100, blursetOptions(undefined, null, 'change'); //500, 100, change
setOptions(undefined, null, 'change'); //500, 0, change
ES2015function setOptions(debounce = 500, interval = 100, event = 'blur') console.info(debounce, interval, event);
setOptions(); //500, 100, blursetOptions(200); //200, 100, blursetOptions(0, 500); //0, 500, blur
SPREADES5
function foo(x,y,z) console.log( x, y, z );foo.apply( null, [1,2,3] ); // 1 2 3
ES2015function foo(x,y,z) console.log( x, y, z );foo( ...[1,2,3] ); // 1 2 3
ANOTHER USAGE OF SPREADlet a = [2,3,4];let b = [ 1, ...a, 5 ];console.log( b ); // [1,2,3,4,5]
RESTfunction foo(x, y, ...z) console.log( x, y, z );foo( 1, 2, 3, 4, 5 ); // 1 2 [3,4,5]
ES5function test() var args = Array.prototype.slice.call( arguments ); //do sth
ES2015function test(...args) //do sth
DESTRUCTURING, PROPERTY ASSIGNSHORTCUTS
ES5function data() return [1, 2, 3];let tmp = data();let a = tmp[0], //a = 1 b = tmp[1], //b = 2 c = tmp[2]; //c = 3
ES2015function data() return [1, 2, 3];let [a,b,c] = data(); // a = 1, b = 2, c = 3
DESTRUCTURING OBJECTSfunction data() return firstName: 'John', lastName: 'Snow', occupation: 'Lord Commander of the Night's Watch' ;var firstName, lastName: a, occupation: b = data();
//firstName = 'John',//a: 'Snow',//b: 'Lord Commander of the Night's Watch'
UNIVERSITY CLASSIC - VARIABLE SWAPvar x = 10, y = 20;[ y, x ] = [ x, y ];console.log( x, y ); // 20 10
DESTRUCTURE NULL OR UNDEFINED =ERROR!
let x, y = null;let a, b = undefined;
HOWEVER UNEXISTING VARIABLESARE OK
let options = a: 1, b: 2;let a, c = options;console.log(a); // 1console.log(b); //undefinedconsole.log(c); //undefined
SYNTAX GOTCHAlet person = id: 1, address: street: 'Przy parku', city: 'Warszawa' ;let id;let address;
id, address = person; //Error(id, address = person); //Ok!
OBJECTS AND ARRAYS AREDESTRUCTURED AS REFERENCES!
let color = name: 'white', rgb: [255, 255, 255];
let name: colorName, rgb = color;console.log(colorName); //'white'console.log(rgb); //[255, 255, 255]console.log(rgb === color.rgb); // true
DESTRUCTURE FUNCTION PARAMS
ES5function setCookie(name, value, options) options = options || ; let secure = options.secure; let path = options.path; let domain = options.domain; //....
setCookie('presentation', 'es2015', secure: false;);
DESTRUCTURE FUNCTION PARAMS
ES2015function setCookie(name, value, secure, path, domain = ) //...
setCookie('presentation', 'es2015', secure: false;);
PROMISES
ES2015 PROMISE SYNTAXvar promise = new Promise((resolve, reject) => setTimeout(resolve, 1000););
REMOVING CALLBACK LEGACY CODEimport LegacyLib from 'LegacyLib';
let internal;class LegacyLibDecorator constructor($q) internal = $q; loadData() let deferred = $q.defer(); LegacyLib.loadData( result => deferred.resolve(result), error => deferred.reject(error) ); return deferred;
REFACTOR USING PROMISE SYNTAXimport LegacyLib from 'LegacyLib';
let internal;class LegacyLibDecorator constructor($q) internal = $q; loadData() return $q((resolve, reject) => LegacyLib.loadData( result => resolve(result), error => reject(error) ); );
Mozilla Hacks: ES6 in depth
ExploringJS Feature overview
Babel learn-es6 Great talk to go to next
http://hacks.mozilla.org/category/es6-in-depthhttp://exploringjs.com/es6/
http://github.com/lukehoban/es6featureshttp://babeljs.io/docs/learn-es2015/
https://www.youtube.com/watch?v=DqMFX91ToLw