Angular

Charles Jacquin

Es2015 (ex ES6)

La Famille JavaScript

ES5

ES2015

TypeScript

  • Classe

  • Modules

  • ...

  • Type Annotation

  • Meta Annotation

  • ...

ES6 ~ ES2015

  • Classes, Inheritance

  • Modules

  • Iterators, Generators

  • Scoping

  • Promises, Proxies

  • Arrow functions

  • Collections

  • ...

TypeScript

  • Type Inference

  • Type compatibility

  • Optional parameters 

  • Enums

  • Interfaces

  • Classes

  • Mixing, Mergins

  • async await

  • ...

ECMAScript?

  • Dernière version finallisé ECMAScript6 (ES6, ES2015, Harmony)

  • Changement de convention de nommage (ES7 = ES2016)

Compatibillité

ECMAScript?

  • Un standard pour les languages de script 

  • Le language le plus populaire est JavaScript

  • ActionScript, Lua pour les plus connus

May '95

Dec '95

Sep '95

1997

Aug '96

1999

1998

2009

2003

2015

2011

2016

Mocha  (Brendan Eich, Netscape)

LiveScript

JavaScript

Edition 1

JScript  (Microsoft)

Edition 2

Edition 3

Edition 4

Edition 5

Edition 5.1

Edition 6

Edition 7

ECMA-262 specification

ES6 Transpilation

  • Un Transpiler est un type de compilateur qui transforme du code d'un language vers un autre

  • ES5 est largement supporté par les browsers

    • Le code ES6 doit être compilé

  • Deux transpiler ES6 -> ES5

    • Traceur (Google)

    • Babel (Community)

ES6 Transpilation

  • Babel est le standard:

    • Le code compilé est plus lisible (sic)

    • Support de JSX (pour React)

  • Une autre alternative consiste à utiliser un preprocesseur JS

    • CoffeeScript

    • Dart

    • Typescript (la suite plus loin)

Traceur ES6 -> ES5 transpilation

/*jshint esnext: true */

import Todo from './todo';
import {values} from './generators';

class TodoList {
  constructor() {
    this.todos = {}; 
    this.length = 0;
  }

  add(text, done = false) {
    let todo = new Todo(text, done);
    this.todos[String(todo.timestamp)] = todo;
    this.length++;
  }

  archiveCompleted() {
    for (let todo of values(this.todos)) {
      if (todo.done) this.delete(todo);
    }
  }

  delete(todo) {
    delete this.todos[String(todo.timestamp)];
    this.length--;
  }

  getUncompletedCount() {
    let count = 0;
    for (let todo of values(this.todos)) {
      if (!todo.done) count++;
    }
    return count;
  }
}

export default TodoList;
$traceurRuntime.registerModule("todoservice.js", [], function() {
  "use strict";
  var __moduleName = "todoservice.js";
  var Todo = $traceurRuntime.getModule($traceurRuntime.normalizeModuleName("./todo", "todoservice.js")).default;
  var values = $traceurRuntime.getModule($traceurRuntime.normalizeModuleName("./generators", "todoservice.js")).values;
  var TodoList = function() {
    function TodoList() {
      this.todos = {};
      this.length = 0;
    }
    return ($traceurRuntime.createClass)(TodoList, {
      add: function(text) {
        var done = arguments[1] !== (void 0) ? arguments[1] : false;
        var todo = new Todo(text, done);
        this.todos[String(todo.timestamp)] = todo;
        this.length++;
      },
      archiveCompleted: function() {
        var $__7 = true;
        var $__8 = false;
        var $__9 = undefined;
        try {
          for (var $__5 = void 0,
              $__4 = (values(this.todos))[Symbol.iterator](); !($__7 = ($__5 = $__4.next()).done); $__7 = true) {
            var todo = $__5.value;
            {
              if (todo.done)
                this.delete(todo);
            }
          }
        } catch ($__10) {
          $__8 = true;
          $__9 = $__10;
        } finally {
          try {
            if (!$__7 && $__4.return != null) {
              $__4.return();
            }
          } finally {
            if ($__8) {
              throw $__9;
            }
          }
        }
      },
      delete: function(todo) {
        delete this.todos[String(todo.timestamp)];
        this.length--;
      },
      getUncompletedCount: function() {
        var count = 0;
        var $__7 = true;
        var $__8 = false;
        var $__9 = undefined;
        try {
          for (var $__5 = void 0,
              $__4 = (values(this.todos))[Symbol.iterator](); !($__7 = ($__5 = $__4.next()).done); $__7 = true) {
            var todo = $__5.value;
            {
              if (!todo.done)
                count++;
            }
          }
        } catch ($__10) {
          $__8 = true;
          $__9 = $__10;
        } finally {
          try {
            if (!$__7 && $__4.return != null) {
              $__4.return();
            }
          } finally {
            if ($__8) {
              throw $__9;
            }
          }
        }
        return count;
      }
    }, {});
  }();
  var $__default = TodoList;
  return {get default() {
      return $__default;
    }};
});
$traceurRuntime.getModule("todoservice.js" + '');

ES5

ES6

Babel ES6 -> ES5 transpilation

/*jshint esnext: true */

import Todo from './todo';
import {values} from './generators';

class TodoList {
  constructor() {
    this.todos = {}; 
    this.length = 0;
  }

  add(text, done = false) {
    let todo = new Todo(text, done);
    this.todos[String(todo.timestamp)] = todo;
    this.length++;
  }

  archiveCompleted() {
    for (let todo of values(this.todos)) {
      if (todo.done) this.delete(todo);
    }
  }

  delete(todo) {
    delete this.todos[String(todo.timestamp)];
    this.length--;
  }

  getUncompletedCount() {
    let count = 0;
    for (let todo of values(this.todos)) {
      if (!todo.done) count++;
    }
    return count;
  }
}

export default TodoList;
/*jshint esnext: true */

'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

var _todo = require('./todo');

var _todo2 = _interopRequireDefault(_todo);

var _generators = require('./generators');

var TodoList = (function () {
  function TodoList() {
    _classCallCheck(this, TodoList);

    this.todos = {};
    this.length = 0;
  }

  _createClass(TodoList, [{
    key: 'add',
    value: function add(text) {
      var done = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];

      var todo = new _todo2['default'](text, done);
      this.todos[String(todo.timestamp)] = todo;
      this.length++;
    }
  }, {
    key: 'archiveCompleted',
    value: function archiveCompleted() {
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = (0, _generators.values)(this.todos)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var todo = _step.value;

          if (todo.done) this['delete'](todo);
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator['return']) {
            _iterator['return']();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }
    }
  }, {
    key: 'delete',
    value: function _delete(todo) {
      delete this.todos[String(todo.timestamp)];
      this.length--;
    }
  }, {
    key: 'getUncompletedCount',
    value: function getUncompletedCount() {
      var count = 0;
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

      try {
        for (var _iterator2 = (0, _generators.values)(this.todos)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
          var todo = _step2.value;

          if (!todo.done) count++;
        }
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2['return']) {
            _iterator2['return']();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }

      return count;
    }
  }]);

  return TodoList;
})();

exports['default'] = TodoList;
module.exports = exports['default'];

ES5

ES6

TypeScript -> ES5 transpilation

// Todo Model
// ----------
// Our basic **Todo** model has `content`, `order`, and `done` attributes.
class Todo {

    // Default attributes for the todo.
    defaults() {
        return {
            content: "empty todo...",
            done: false
        }
    }

        // Ensure that each todo created has `content`.
    initialize() {
        if (!this.get("content")) {
            this.set({ "content": this.defaults().content });
        }
    }

    // Toggle the `done` state of this todo item.
    toggle() {
        this.save({ done: !this.get("done") });
    }

    // Remove this Todo from *localStorage* and delete its view.
    clear() {
        this.destroy();
    }

}
// Todo Model
// ----------
// Our basic **Todo** model has `content`, `order`, and `done` attributes.
var Todo = (function (_super) {
    __extends(Todo, _super);
    function Todo() {
        _super.apply(this, arguments);
    }
    // Default attributes for the todo.
    Todo.prototype.defaults = function () {
        return {
            content: "empty todo...",
            done: false
        };
    };
    // Ensure that each todo created has `content`.
    Todo.prototype.initialize = function () {
        if (!this.get("content")) {
            this.set({ "content": this.defaults().content });
        }
    };
    // Toggle the `done` state of this todo item.
    Todo.prototype.toggle = function () {
        this.save({ done: !this.get("done") });
    };
    // Remove this Todo from *localStorage* and delete its view.
    Todo.prototype.clear = function () {
        this.destroy();
    };
    return Todo;
}(Backbone.Model));

ES5

ES6

Modules

  • ES6 amène la standardisation

    • ​Actuellement: AMD, CommonJS

    • Avant: Rien (global)

  • Avec ECMAScript 6, les modules sont des fichiers

    • un module par fichier

    • un fichier par module

Modules

  • Une donnée exportée dans un module est accessible partout où le module est importé

// ClientService.js

class ClientService {
    
    constructor() {
        this.clients = [];    
    }

    addClient(client) {
        this.clients.add(client);
    }

}

export default new ClientService();
import ClientService from './ClientService';

class ClientComponent {

    submitClient(client) {

        ClientService.addClient(client);

    }

}

ES6

ES6

module.exports = ClientService;

CommonJS

export default ClientService;

ES6

var Service = require('./ClientService');
import Service from ('./ClientService');

Modules (ES6 vs CommonJS)

Modules

  • Default exports

    • Un seul par module

    • Lors de l'import, le nom du module est libre

// ClientService.js

class ClientService {
    
    constructor() {
        this.clients = [];    
    }

    addClient(client) {
        this.clients.add(client);
    }

}

export default new ClientService();

ES6

import ClientService from './ClientService';

ES6

import MyService from './ClientService';

class ClientComponent {

    submitClient(client) {

        MyService.addClient(client);

    }
}

Modules

Default exports

//------ MyClass.js ------
class MyClass { ... };
export default MyClass;

//------MyFunction.js ----
function myFunction() { ... };
export default myFunction;
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

Named exports

// ----- main.js ---
import * as lib from './lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
// ----- main.js ---
import MyClass from './MyClass'
import thatFunction from './myFunction'

var my = new MyClass();
var res = thatFunction();
// Constants.js

var $BANKRUPTCY_MIN_DEBT = 1001;
var $SEQUESTRATION_MIN_DEBT = 3000;

export $BANKRUPTCY_MIN_DEBT;
export $SEQUESTRATION_MIN_DEBT;

ES6

Modules (named exports)

// Constants.js

var $BANKRUPTCY_MIN_DEBT = 1001;
var $SEQUESTRATION_MIN_DEBT = 3000;

export { 
  $BANKRUPTCY_MIN_DEBT,
  $SEQUESTRATION_MIN_DEBT
}

ES6

  • Plusieurs export par module

  • Peut être mixer avec default

import {
  $BANKRUPTCY_MIN_DEBT, 
  $SEQUESTRATION_MIN_DEBT
} from './Constants';

//do something

Modules (actuel)

  • CommonJS

  • AMD

// someModule.js
function doSomething () {
    console.log("foo");
}

exports.doSomething = doSomething;


//otherModule.js
var someModule = require('someModule');    
someModule.doSomething();
// someModule.js
define(["lib"], function (lib) {
    function doSomething() {
           console.log("foo");
    }
    return {
        doSomething: doSomething
    }
});

//otherModule.js
require(["someModule"], function(someModule){
    someModule.doSomething();
});
  • Syntaxe compacte

  • chargement synchrone

  • node.js, browserify, webpack

  • Chargement asynchrone

  • RequireJS

  • Renommage à l'import:

Modules

ES6

ES6

import {$BANKRUPTCY_MIN_DEBT as minBank,
    $SEQUESTRATION_MIN_DEBT as minSeq
    } from './Constants';

function isEligibleForBankruptcySequestration(
    isScottish, debtTotal) {
   
    var minDebt;

    if(isScottish) { minDebt = minSeq }
        else { minDebt = minBank };

    return debtTotal >= minDebt;

}
// Constants.js

var $BANKRUPTCY_MIN_DEBT = 1001;
var $SEQUESTRATION_MIN_DEBT = 3000;

export $BANKRUPTCY_MIN_DEBT;
export $SEQUESTRATION_MIN_DEBT;

Fonctionnalitées ES2015

Déclarations de variables

let foo = 4;

foo = 5;

const bar = {};

bar.foo = 'bar';

lorsqu'il est utilisé dans une boucle le scope de let est limité à la boucle.

(ce n'était pas le cas avec "var")

function doSomething() {
  let i = 4;
  for (let i = 0; i < 50; i++) {
    console.log(i)
  }
  console.log(i):
}

String interpolations

const name = 'white';
const myOldSchoolString = 'yo Mr ' + name;

const myEs2015String = `yo Mr ${name}`;

Arrow function (lambda)

Une arrow function est une fonction qui ne crée pas de nouveaux contexte this.

const myLambda = () => {

}

//parfait pour les callbacks
myAsyncCall(myParam, (data) => {

})

myPromise()
  .then(data => {

  })
  .catch(err => {

  })

Plus de 

var self = this;

Si il n y a qu'un seul paramètre, les parenthèses sont optionnelles

...spread

Spread accepte une collection (Array ou Object)

const add = (a, b) => a + b;
let args = [3, 5];
add(...args);
let cde = ['c', 'd', 'e'];
let scale = ['a', 'b', ...cde, 'f', 'g']; 
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']
let mapABC  = { a: 5, b: 6, c: 3};
let mapABCD = { ...mapABC, d: 7}; 
// { a: 5, b: 6, c: 3, d: 7 }

S'utilise lors de l'appel de la fonction

...rest

Rest partage la syntaxe de Spread, mais permet à une fonction d’accéder à un nombre variable de paramètres.

function add(...numbers) {
  return numbers[0] + numbers[1];
}
add(3, 2);        // 5
function print(a, b, c, ...more) {
  console.log(more[0]);
  console.log(arguments[0]);
}
print(1, 2, 3, 4, 5); // 4  1

S'utilise lors de la déclaration de la fonction

//modern style
const addEs6 = (...numbers) => numbers.reduce((p, c) => p + c, 0);

addEs6(1, 2, 3);  // 6

Les Promises

Abstraction qui permet d'écrire du code asynchrone lisible et maintenable.

function myPromisedTimer (timer) {
  return new Promise((resolve, reject) => {
    if(typeof timer !== 'number')
      return reject(new Error('timer must be a number'))
    setTimeout(() => {
      resolve(`${timer} second later`);
    },timer * 1000)
  })
}

myPromisedTimer(6)
  .then(message => console.log(message))
  .catch(err => console.error(err))

Pour les vieux navigateurs, il faudra utiliser un polyfill.

Les Promises (suite)

Enchainement

function myPromisedTimer (timer) {
  return new Promise((resolve, reject) => {
    if(typeof timer !== 'number')
      return reject(new Error('timer must be a number'))
    setTimeout(() => {
      resolve(timer * 2);
    },timer * 1000)
  })
}

myPromisedTimer(6)
  .then(newTimer => {
    return myPromisedTimer(9);
  })
  .then(newTimer => {
    return myPromisedTimer(2)
  })
  .catch(err => console.error(err))

Les Promises (suite)

Traitements parrallèles

Promise.all([
  myPromisedTimer(4),
  myPromisedTimer(8),
  anotherPromise()
])
  .then(response => {
    //response is an array of 3
  })
  .catch(err => console.error(err))

Les Classes

export class User {
  constructor(name) {
    this.name = name
  }

  doSomething() {
    console.log(this.name);
  }

  static aStaticMethod() {
    
  }
}

const myUser = new User('toto')
export function User(name) {
  this.name = name
}

User.prototype.doSomething() {
  console.log(this.name);
}

User.aStaticMethod() {
    
}

var myUser = new User('toto')
class Client {

    constructor(firstname, lastname) {
        this.id = undefined
        this.firstname = firstname
        this.lastname = lastname
    }

    getFullName() {
        return this.firstname + this.lastname
    }

    generateId() {
        this.id = generateGuid()
    }

    static generateGuid() {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
            .toString(16)
            .substring(1)
        }
        
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
        s4() + '-' + s4() + s4() + s4()
    }
    
}
  • Pas de "this"

Les Classes

Méthode statique

Client.generateGuid()
  • Appelable directement depuis la classe :

Les Classes

import {Person} from './Person'

export class User extends Person {
  constructor(name, email) {
    super(email)
    this.name = name
  }

  doSomething() {
    console.log(this.name);
  }
}

const myUser = new User('toto', 't@gm')

myUser.sendEmail()
export class Person {
  constructor(email) {
    this.email = email
  }
  
  sendEmail() {

  }
}

Héritage

class DRClient extends Client {

    this.vcNumberPrefix = "W"

    generateVCNumber() {
        super.generateVCNumber()
    }

}
var TCSClient = Object.create(Client)

TCSClient.vcNumberPrefix = "A"

TCSClient.prototype.generateVCNumber = function() {
    Client.prototype.generateVCNumber.call(this)
}

ES5

ES6

function Client () {
    this.vcNumber = undefined
    this.vcNumberPrefix = ""
}

Client.prototype.generateVCNumber = function () {
    var number = Math.floor(Math.random() * 10000000)
    this.vcNumber = this.vcNumberPrefix + pad(number, 7)
}

ES5

Classe parent (ES5)

  • Appel d'une méthode du même nom avec .call(this)

  • utilisation du mot clef super

Les Classes

Héritage

Typescript

TypeScript?

  • Gratuit, Open Source, développé par Microsoft

  • Extension d'ECMAScript 6

  • JavaScript fortement typé

    • Détection de certaines erreurs dès la compilation

    • Auto-complétion puissante et fiable

    • Mis au point par Anders Hejlsberg, Lead Architect C#

    • Plus proche de Java ou C# que de JavaScript

  • Sucre syntaxique

    • Rend le code plus concis et lisible

  • Compilateur TypeScript/ES6 => ES5

    • Compatible avec tous les navigateurs

    • Les librairies JavaScript existantes sont utilisables dans les projets TypeScript

  • Conçu pour faciliter la maintenance des grosses applications JavaScript

  • Le typage statique, on aime ou on n'aime pas...

TypeScript?

Angular2 & TypeScript

Angular 2 est développé en TypeScript, ce qui en fait le language le plus populaire pour développer avec ce framework

Il est tout à fait possible de développer une application angular en utilisant ES5, ES2015 ou même Dart.

Les variables sont fortement typées, explicitement ou implicitement

 

var x:number = 1
var n = 1                         // var n:number = 1

var s = "Hello World"             // var s:string = "Hello World"

n = s;                            // error
s = n;                            // error

function f() {                    // function f(): string {
    return "hello"                
}

Typage

window.onmousedown = function(mouseEvent) { 
    console.log(mouseEvent.button);  // <- Error  
};

window.onmousedown = function(mouseEvent: any) { 
    console.log(mouseEvent.button);  // <- OK 
};

TypeScript

Type "any"

  • Offre la flexibilité du typage dynamique propre à JS
  • On l'utilise quand on ne connait pas le type à priori

type de retour d'une fonction

function add(num1, num2): number {
    return num1 + num2;
}

function concat(str1, str2): string {
    return str1 + str2;   
}
  • type 'void'

    • absence de type

    • ne retourne rien

function warnUser(): void {
    alert("This function doesn't return a type");
}

Typage

et les paramètres

function add(num1: number, num2: number) {
    return num1 + num2;
}

function concat(str1: string, str2: string) {
    return str1 + str2;   
}

TypeScript

  • Les paramètres sont toujours optionnel en javascript

  • Avec Typescript les paramètres sont toujours obligatoires a moins d'être suffixés par un "?"

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

var result1 = buildName("Bob");  //works correctly now
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right

TypeScript

Paramètre optionnel

Paramètres par défaut

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  //works correctly now, also
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right
  • Définit la structure d'un objet

interface LabelledValue {
    label: string;
}

function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label)
}

var myObj = { size: 10, label: "Size 10 object" };
printLabel(myObj);

'labelledObj' doit posseder un attribut 'label' de type string (Structural Typing)

Interfaces

propriété optionnelle ('?')

interface LabelledValue {
    label: string;
    size?: number;
}

Interfaces

function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label)
}

var myObj = { count: 10, label: "Size 10 object" };
printLabel(myObj);    // ok


var myObj = { size: 10, lbl: "Size 10 object" };
printLabel(myObj);    // error - myObj does not satisfy LabelledValue
interface SearchFunc {
    (source: string, subString: string): boolean;
}
var mySearch: SearchFunc;

mySearch = function(src: string, sub: string) {
    var result = src.search(sub);
    if (result == -1) {
        return false;
    }
    else {
        return true;
    }
}

Interfaces

Pour définir une signature de fonction...

Fichier de déclaration

Un fichier .d.ts est nécessaire pour utiliser une librairie JS tierce

Ce fichier fournit au compilateur TypeScript les déclarations de types pour l'API de cette librairie

La plupart des librairies mettent à disposition un fichier .d.ts :

  • directement via npm
  • via un dépôt de définitions
var Geometry = {

  point: (function () {
    function point(x, y) {
      this.x = x;
      this.y = y;
    }
    return point;
  }()),

  line: (function () {
    function line(p1, p2) {
      this.point1 = {x: p1.x, y: p1.y};
      this.point2 = {x: p2.x, y: p2.y};
    }
    return line;
  }()),

  distance: function (line) {
    var p1 = line.point1;
    var p2 = line.point2;
    return Math.sqrt(Math.pow(p2.x - p1.x, 2) 
         + Math.pow(p2.y - p1.y, 2));
  }

};

Geometry.js

declare module MGeometry {

}

Geometry.d.ts

Une déclaration de module:

declare module MGeometry {

    export interface Main {
        
    }

}

Une interface représentant l'API de notre module:

var Geometry = {

  point: (function () {
    function point(x, y) {
      this.x = x;
      this.y = y;
    }
    return point;
  }()),

  line: (function () {
    function line(p1, p2) {
      this.point1 = {x: p1.x, y: p1.y};
      this.point2 = {x: p2.x, y: p2.y};
    }
    return line;
  }()),

  distance: function (line) {
    var p1 = line.point1;
    var p2 = line.point2;
    return Math.sqrt(Math.pow(p2.x - p1.x, 2) 
         + Math.pow(p2.y - p1.y, 2));
  }

};

Geometry.js

declare module MGeometry {

    export interface Main {
        point:Point;
        line:Line;
        distance(line:Line):number;    
    }

}

Interfaces décrivant les membres:

    export interface Point {
        x:number;
        y:number;
        new(x:number, y:number):Point;
    }

    export interface Line {
        point1:Point;
        point2:Point;
        new(p1:Point, p2:Point):Line;
    }

Decorators

import {Person} from './Person'

@Component()
export class User implements Person {
  @Input() name: string;
  
  constructor(@Inject foo: Bar) {}
}

Les décorateurs sont de simples fonctions.

On les utilise comme des annotations en Java.

4 types de decorateurs : 

  • class

  • property

  • method

  • parameter

Environnement de développement

Environnement de développement.

Npm est le gestionnaire de paquet pour NodeJS.

$ npm search nom-du-module
$ npm install nom-du-module
$ npm install nom-du-module -g
$ npm install
$ npm update

Package.json

  "dependencies": {
    "@angular/http": "2.0.0-rc.2",
    "@angular/common": "2.0.0-rc.2",
    "@angular/compiler": "2.0.0-rc.2",
    "@angular/core": "2.0.0-rc.2",
    "@angular/forms": "^0.1.0",
    "@angular/platform-browser": "2.0.0-rc.2",
    "@angular/platform-browser-dynamic": "2.0.0-rc.2",
    "@angular/platform-server": "2.0.0-rc.2",
    "@angular/router": "3.0.0-alpha.7",
    "@angular/router-deprecated": "2.0.0-rc.2",

    "ie-shim": "^0.1.0",
    "core-js": "^2.4.0",
    "rxjs": "5.0.0-beta.9",
    "zone.js": "~0.6.12"
  }

webpack

module bundler.

The

  • production

  • unbiased

  • flexible

  • extensible

  • open source

Webpack va prendre en charge le build et le développement de A à Z.

Installation

$ npm i webpack webpack-dev-server -D

Une fois l'installation terminée, nous avons accés à ces deux éxécutables dans les "scripts" du package.json

{
    "scripts": {
        "build:prod": "webpack --progress -p",
        "server:dev": "webpack-dev-server --inline --progress"
    }
}

webpack.config.js

module.exports =  {
  debug: true,
  cache: true,
  devtool: 'cheap-module-eval-source-map',
  entry: {
    vendor: './src/vendor.js',
    main: './src/app.js',
  },
  output: {
    path: 'dist',
    filename: '[name].bundle.js',
    sourceMapFilename: '[name].map',
    chunkFilename: '[id].chunk.js'
  },
  plugins: [
    new DefinePlugin({
      'ENV': JSON.stringify(process.env.ENV),
    }),
  ],
  module: {
    loaders: [
      { test: /\.js$/, loader: 'babel-loader' },
      { test: /\.css$/, loader: 'style!css' },
      { test: /\.html$/, loader: 'raw-loader' },
    ]  
  }
});

Installation de typescript

$ npm i typescript -g
$ npm i typings -g

Typings

Vous devrez parfois définir des interfaces pour utiliser certaines librairies

$ typings install lodash --save

angular-cli

Mock un webservice REST grace à un fichier .json.

$ npm i json-server -g
$ json-server db.json --port 3002

Introduction

Architecture Angular 2

<app></app>

menu

grid

gallery

DI
(classes ES6 ou TypeScript)
 

Pipes
(classes ES6 ou TypeScript)
 

@Component

@View

@Directive

@Animation

@Inject

@InjectLazy

@Optional

@Host

@Parent

@Pipe

@Property

@Event

@RouteConfig

@HostBinding

@HostEvent

@ContentChildren

@ViewChild

@ViewChildren

@Input

@Output

@Attribute

@CanActivate

 

Les Web Components

Custom Elements

Templates

Imports

Shadow DOM

Shadow DOM

Shadow DOM

Encapsule le html et le css à la manière d'un objet

Avant le ShadowDom la seule manière d'isoler du code était l'iframe

Les composants

@Component

  • Brique fondamentale d'une application Angular 2

  • Une application EST un arbre de composants

  • Utilisation de métadonnées pour configurer un composant de manière déclarative

//<my-app></my-app>
function MyAppComponent() {

}

MyAppComponent.annotations = [
    new angular.ComponentAnnotation({
        selector: 'my-app'
    }),
    new angular.ViewAnnotation({
        template: "<main>" + 
                       "<h1> This is my first Angular2 app</h1>" +
                  "</main>"
    })
];

Composant version ES5

import {Component} from '@angular/core';

@Component({
    selector: 'my-app',
    template: `<main>
                 <h1> This is my first Angular2 app</h1>
               </main>`
})
class MyAppComponent {

}

Composant version TypeScript

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

Modules

// The browser platform with a compiler
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

// The app module
import { AppModule } from './app.module';

// Compile and launch the module
platformBrowserDynamic().bootstrapModule(AppModule);

Bootstrap de l'Application

{{ expression }}

template: `<h1>{{"My First Angular2 app"}}</h1>`
template: `<h1>My First Angular2 app</h1>`
@Component({
    selector: 'app',
    template: `<h1>{{ message }}</h1>`
})
class AppComponent {
    message = '<h1> This is my first Angular2 app</h1>'
}

Binding

RootComp

Child1Comp

Child2Comp

[property]="expression"

(event)="update()"

@Input()
@Output()

Property Binding

<input [attr]="expression" />
  • Accès à toutes les propriétés des éléments du DOM

  • Possibilité de définir de nouvelles propriétés

  • Compatibilité avec d'autres spécifications

@Input()

Property Binding

import {Component} from '@angular/core';

@Component({
  selector: 'greeter',
  inputs: ['name'],
  template: `<h1>Hello, {{name}}</h1>`
})
export class Greeter {
  name: string;
}
<greeter [name]="'John'"></greeter>

@Input()

import {Component, Input} from '@angular/core'

@Component({
  selector: 'greeter',
  template: `<h1>Hello, {{ name }}</h1>`
})
export class Greeter {
  @Input() name: string;
}
<greeter [name]="'John'"></greeter>

Event Binding

<input (event)="expression" />
  • Accès à tous les évènements natifs du DOM

  • Possibilité de définir de nouveaux événements

@Output()

@Output()

import {Component, Input, Output, EventEmitter} from '@angular/core'

@Component({
  selector: 'greeter-button',
  template: `<button (click)="onClick()">
              Greet Me!
             </button>`
})
export class GreeterButton {
  @Input() name: string;
  @Output() greet = new EventEmitter<string>();
  onClick() {
    this.greet.emit(this.name);
  }
}
<greeter-button [name]="'John'"
                (greet)="doSomething($event)">
</greeter-button>

Styles

import {Component, EventEmitter, Input, Output} from '@angular/core';

@Component({
  selector: 'user-item',
  styles: [`
    .user {
      color: brown;
    }
  `],
  template: `<section>
                <h2 class="user">{{name}}</h2>
                <button (click)="selectUser()">Send Message</button>
            </section>`
})
class UserItem {
  @Input() name: string;
  @Output() sendMessage: EventEmitter = new EventEmitter<string>();
  
  selectUser() {
     this.sendMessage.next(this.name);
  }
}

ngContent

import {Component, Input} from '@angular/core';

@Component({
  selector: 'tab',
  template: `<section class="tab">
                <h1>This is a Tab</h1>
                <!-- Le contenu de la balise <tab> sera inséré ici -->
                <ng-content></ng-content> 
              </section>`
})
export class Tab {}
<tab>
  <h2>Tab Content</h2>
</tab>

Component Dependency

  • Nécessité d'importer les composants utilisés dans le Template

  • Propriété directives de @Component

  • Erreur si composants non utilisés

Component Dependency

import {Component} from '@angular/core';
import {UserItem} from './user-item.component';

@Component({
    selector: 'user-list',
    template: `<div *ngFor="let user of users">
                  <user-item [name]="user"></user-item>
               </div>`,
    directives: [UserItem]
})
export class UserList {
    users: string[] = ['John', 'Aria'];
}
<user-list></user-list>

Lifecycle Hooks

@Component()
export class myComponent { 
  constructor() { }
  ngOnInit() {}
  ngOnDestroy() {}
  ngDoCheck() {}
  ngOnChanges(records) {}
  ngAfterContentInit() {}
  ngAfterContentChecked() {}
  ngAfterViewInit() {}
  ngAfterViewChecked() {}
}

Callback appelés à différents moments de la vie du component.

Bonnes pratiques

import {Component, OnInit} from '@angular/core'

@Component()
export class myComponent implements OnInit{ 
  constructor() { /* Injection de dépendance */ }
 
  ngOnInit() { /* code à executer à l'initialisation */ } 
 
}

Utiliser les interfaces !!!

Au lieu de charger le constructeur pour rien, privilégier l'utilisation de ngOnInit()

Manipulation du DOM

import {Component, ElementRef, OnInit} from '@angular/core';
@Component({
  selector: 'todo-app',
  template: `...`
})
export class TodoApp implements OnInit{
  constructor(private elementRef: ElementRef) {}

  ngOnInit() {
    const container = this.elementRef.nativeElement;
  }
}

ElementRef permet de manipuler l'élément html natif.

Vous pouvez donc intégrer nimporte quelle librairie native javaScript (d3.js par exemple)

Support des WebComponents

WebComponents

@Component({
    ...

    encapsulation?: ViewEncapsulation,

    ... 
})
class UserItem { ... }

WebComponents

//From modules/angular2/src/core/render/api.ts

/**
 * How the template and styles of a view should be encapsulated.
 */
export enum ViewEncapsulation {
  /**
   * Emulate scoping of styles by preprocessing the style rules
   * and adding additional attributes to elements. This is the default.
   */
  Emulated,
  /**
   * Uses the native mechanism of the renderer. For the DOM this means creating a 
   * ShadowRoot.
   */
  Native,
  /**
   * Don't scope the template nor the styles.
   */
  None
}

Directives

3 types de directives

  • Composants

  • Attributs

  • Structurelles

Seul le composant possède un template

Modifie l'apparence ou le comportement d'un élément (exemple: NgStyle)

S'utilise comme un attribut HTML

import {Directive} from '@angular/core'

@Directive({
  selector: '[my-directive]'
})
export class MyDirective {

}

Attribute Directives

<p [my-directive]="data">Results may vary</p>

ngStyle

<div [style.background-color]="'yellow'">
  Uses fixed yellow background
</div>
<div [ngStyle]="{color: 'white', 'background-color': 'blue'}">
  Uses fixed white text on blue background
</div>

ngStyle 

<input type="color" #colorInput />
<input type="number" #fontSize />

<div [ngStyle]="{color: colorInput.value}" 
     [style.font-size.px]="fontSize">

ngClass

<div [ngClass]="{bordered: false}">This is never bordered</div>
<div [ngClass]="{bordered: true}">This is always bordered</div>

<div [ngClass]="{bordered: isBordered}">
  This is a div with object literal. 
  Border is {{ isBordered ? "ON" : "OFF" }}
</div>
.bordered {
  border: 1px dashed black;
  background-color: #eee;
}

Manipule le DOM pour ajouter ou supprimer des éléments (par exemple: NgIf, NgFor)

On utilise le préfixe "*" (astérisque)

<p *ngIf="condition">
  condition is true and ngIf is true.
</p>

Structural Directives

ngFor

import {Component} from '@angular/core'

@Component({
  selector: 'todo-list',
  template: `
    <h2>Todos</h2>
    <ul>
      <li *ngFor="let todo of todos">{{todo}}</li>
    </ul>
  `
})
export class TodoList {
  constructor() {
    this.todos = ['Walk the dog', 'Stay in bed', 'Code more']
  }
}

Le mot clef let remplace le # de la beta

ngFor : index

import {Component} from '@angular/core'

@Component({
  selector: 'todo-list',
  template: `
    <h2>Todos</h2>
    <ul>
      <li *ngFor="let todo of todos; let i = index">
        {{i}} : {{todo}}
      </li>
    </ul>
  `
})
export class TodoList {
  constructor() {
    this.todos = ['Walk the dog', 'Stay in bed', 'Code more']
  }
}

ngIf

<div *ngIf="false"></div>
<div *ngIf="a > b"></div>
<div *ngIf="str == 'yes'"></div>
<div *ngIf="myFunc()"></div>

Permet d'afficher un élément selon la valeur d'une expression booléenne.

Une alternative utilisant du css consiste à utiliser :

<div [hidden]="someProp">I am hidden</div>

ngSwitch

<div *ngIf="myVar == 'A'">Var is A</div>
<div *ngIf="myVar == 'B'">Var is B</div>
<div *ngIF="myVar == 'C'">Var is C</div>
<div *ngIf="myVar != 'A' && myVar != 'B' && myVar != 'C'"></div>
<div class="container" [ng-switch]="myVar">
  <div *ngSwitch-when="A">Var is A</div>
  <div *ngSwitch-when="B">Var is B</div>
  <div *ngSwitch-when="C">Var is C</div>
  <div *ngSwitch-default>Var is something else</div>
</div>

Directive complexe

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[myHighlight]'
})
export class HighlightDirective {
  @Input('myHighlight') highlightColor: string;
  private el: HTMLElement;
  constructor(el: ElementRef) {
    this.el = el.nativeElement;
  }
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || 'cyan');
  }
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }
  private highlight(color: string) {
    this.el.style.backgroundColor = color;
  }
}

Pipes

Petit Rappel


{{ collectionOfUsers | orderBy:'firstName' | limitTo:5 }}

Pipes

  • Identiques aux filtres d'AngularJS 1

  • Permet de manipuler une donnée

  • Utilisation d'une classe annotée @Pipe

  • Pipes disponibles dans le framework : 

    • uppercase, lowercase, async, number, slice, json et date

Declaration

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'uppercase'
})
export class UpperCasePipe implements PipeTransform {
  transform(value: String, args: any[]) {
    
    return value.toUpperCase();
  
  }
}

Utilisation

import {Component, View} from '@angular/core';
import {UpperCasePipe} from './UpperCasePipe'

@Component({
  selector: 'widget1',
  template: `<div>{{'Démo utilisant les pipes' | uppercase}}</div>`,
  pipes: [UpperCasePipe]
})
export class Widget1{}

Pipe as a Service

import {Component, View} from '@angular/core';
import {UpperCasePipe} from './UpperCasePipe'

@Component({
  selector: 'widget1',
  template: ``,
  providers: [UpperCasePipe] 
})
export class Widget1{
    constructor(public upperCasePipe:UpperCasePipe){
      this.upperCasePipe.transform('Un autre exemple...');
    }
}

Architecture orientée service

Injection de dépendances

Injection de Dépendances

DI version Angular2

  • 1 Injecteur principal + 1 Injecteur par composant

    • Hérite de l'injecteur parent

    • Possibilité de redéfinir le Service à injecter

  • Utilisation d'annotations en ES6 et des types en TypeScript

  • Services disponibles via le constructeur du composant

@Injectable

Création de service

import {Injectable} from '@angular/core';

@Injectable()
export class UserService {
  private name: string

  getUserName(): string {
    return this.name;
  }
}
import {UserService} from './user.service';

@Component({
    selector: 'my-app',
    template: `<main>
                 <h1> Welcome {{userName}}</h1>
               </main>`,
    providers: [UserService]
})
class MyAppComponent {
    userName: string;
    constructor(private userService: UserService) { 
        this.userName = this.userService.getUserName();
    }
}

useClass

@Component({
    selector: 'my-app',
    template: `<main>
                 <h1> Welcome {{userName}}</h1>
               </main>`,
    providers: [
        { provide: UserServiceInterface, useClass: UserServiceImpl }
    ]
})
class MyAppComponent {
  userName: string;
  constructor(private userService: UserServiceInterface) { 
    this.userName = this.userService.getUserName();
  }
}

Injecteur global

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';
import UserService from './user.service'

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap: [ AppComponent ],
  providers: [ UserService ]
})
export class AppModule { }

App

ItemsEdition

ItemsList

C

D

E

providers: [ItemsRepository]

Toute l'application partage la même instance du service.

Providers visibility

App

ItemsEdition

ItemsList

C

D

E

providers: [ItemsRepository]

Chaque sous-arbre dispose de sa propre instance du service.

providers: [ItemsRepository]

Deprecated Router

(Déprécié depuis la rc1)

import {provide}          from '@angular/core';
import {
    PathLocationStrategy, // or HashLocationStrategy,
    LocationStrategy
}                         from '@angular/common';
import {bootstrap}        from '@angular/platform-browser-dynamic';
import {ROUTER_PROVIDERS} from '@angular/router-deprecated';
import {AppComponent}     from './app.component';

bootstrap(AppComponent, [
    ROUTER_PROVIDERS,
    provide(LocationStrategy, {
        useClass: PathLocationStrategy // or HashLocationStrategy
    })
]);

Bootstrap du router

Les routes pointent vers des composants.

import {Component} from '@angular/core';
import {RouteConfig} from '@angular/router-deprecated';


@RouteConfig([
    {path: '/', component: Home, name: 'Home'},
    {path: '/list', component: Items, name: 'List'}
]}
@Component({..})
export class AppComponent{}
<router-outlet></router-outlet>

En fonction de l' url, un composant sera chargé à l'intérieur du composant router-outlet

Routes paramètrées

import ...

@RouteConfig([
    {path: '/item/:id', component: Item, name: 'Item'}
]}
@Component({...})
class ...
import ...

@Component({})
export class Item {
  constructor(params:RouteParams) {
    let routeParamValue:string = params.get('id');
  }
}

<router-outlet>

<router-outlet>

<router-outlet>

Routes Imbriquées

@RouteConfig([
    {path: '/home', component: Home, as: 'Home'},
    {path: '/items/...', component: Items, as: 'List'}
]}
@Component({..})
class ...
<router-outlet></router-outlet>
@RouteConfig([
    {path: '/add', component: AddItem, as: 'Add'},
    {path: '/edit/:id', component: EditItem, as: 'Edit'}
]}
@Component({..})
class ...
<router-outlet></router-outlet>

Exemple

/home

/items/add

/items/edit/1

Home

Items

Items

AddItem

EditItem

<a [routerLink]="['Home']">Home</a>
<a [routerLink]="['Items', 'Add']">Home</a>
<a [routerLink]="['Items', 'Edit', {id: 99}]">99th item</a>

Navigation

import {RouteParams, Router} from '@angular/router-deprecated';
import ...

@Component({...})
export class Item {
  constructor(private router: Router, 
              private routeParams: RouteParams) {}
  
  getId(): number() {
    return this.routeParams.get('id');
  }

  backHome(): void {
    this.router.navigate('Home');
  }
  
}

Navigation

import {
    CanActivate,
    ComponentInstruction} from '@angular/deprecated-router';

@CanActivate(
  (next: ComponentInstruction,
   prev: ComponentInstruction) => 
  {
    return confirm('are you sure you want to go here?')
  }
)
export class ComponentTwo implements CanActivate {}

Cycle de vie

CanActivate

Possibilité de restreindre l'accés à une route.

Cette fonction retourne un booléen ou une promise.

Utilise une annotation, car s'éxécute avant l'instanciation du component.

import {
    CanDeactivate,
    ComponentInstruction} from '@angular/deprecated-router';

export default class ComponentTwo implements CanDeactivate {
 // ...
  routerCanDeactivate(next: ComponentInstruction,
                      prev: ComponentInstruction) 
  {
    return confirm('Are you sure you want to leave?');
  }
}

Cycle de vie

CanDeactivate

Fonctionne comme CanActivate

Cette fonction retourne un booléen ou une promise.

Router

import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {rootRouterConfig} from './app.routes';
import {AppComponent} from './app.component';
import {BrowserModule} from '@angular/platform-browser';
import {AboutComponent} from './about/about.component';
import {HomeComponent} from './home/home.component';

@NgModule({
  declarations: [AppComponent, AboutComponent, HomeComponent],
  imports     : [BrowserModule, RouterModule.forRoot(rootRouterConfig)],
  bootstrap   : [AppComponent]
})
export class AppModule {

}

Bootstrap du router

Les routes pointent vers des composants.

import {Routes} from '@angular/router';
import {AboutComponent} from './about/about.component';
import {HomeComponent} from './home/home.component';

export const rootRouterConfig: Routes = [
  {path: '', redirectTo: 'home', pathMatch: 'full'},
  {path: 'home', component: HomeComponent},
  {path: 'about', component: AboutComponent},
];

<router-outlet></router-outlet>

En fonction de l' url, un composant sera chargé à l'intérieur du composant router-outlet

Routes paramètrées

import ...
import {ActivatedRoute} from '@angular/router';

@Component({
  selector: 'item-details',
  template: `<h1>{{ id$ | async }}</h1>`
  
})
export class ItemDetails {
  id$: Observable<string>;

  constructor(routeParams: ActivatedRoute) {
    this.id$ = routeParams.params
        .map((params) =>  params.id)
  }
}

<router-outlet>

<router-outlet>

<router-outlet>

Routes Imbriquées

Exemple

import ...
import { Routes } from '@angular/router';


export const todoRoutes: Routes = [
  {path: '', component: Home},
  {
    path: 'todo',
    component: TodoComponent,
    children: [
      {path: 'detail/:id', commponent: DetailsComponent },
      {path: 'list', commponent: ListComponent }  
    ]
  },
]

Ne pas oublier le tag router-outlet dans le template du  TodoComponent

/ home

/items/add

/items/edit/1

Home

Items

Items

AddItem

EditItem

<a [routerLink]="['/']">Home</a>
<a [routerLink]="['/items/add']">Home</a>
<a [routerLink]="['/items/edit', 99]">99th item</a>

Navigation

Les formulaires

2 ways bindings

Par défaut le data-binding est unidirectionnel.

<input [ngModel]="todo.text" (ngModelChange)="todo.text=$event"/>

<input [(ngModel)]="todo.text"/>

Il est toutefois possible d'utiliser un data-binding bi-directionnel.

<input type="text" #username/>
{{username.value}}

Template driven form

import { NgModule }     from '@angular/core';
import { FormsModule }  from '@angular/forms';


@NgModule({
  imports: [ FormsModule ],
    ...
})
...

Template driven form

<label for="name">Name</label>

<input type="text" id="name" class="form-control"
       required minlength="4" maxlength="24"
       name="name" [(ngModel)]="hero.name"
       #name="ngModel" >

<div *ngIf="name.errors && (name.dirty || name.touched)"
     class="alert alert-danger">
    <div [hidden]="!name.errors.required">
      Name is required
    </div>
    <div [hidden]="!name.errors.minlength">
      Name must be at least 4 characters long.
    </div>
    <div [hidden]="!name.errors.maxlength">
      Name cannot be more than 24 characters long.
    </div>
</div>

Reactive Form

import { NgModule }     from '@angular/core';
import { ReactiveFormsModule }  from '@angular/forms';


@NgModule({
  imports: [ ReactiveFormsModule ],
    ...
})
...

FormControl

Un FormControl est un object qui représente un input .

Il permet notamment la validation.

import {Component} from '@angular/core';
import {FormControl, Validators} from '@angular/forms';

@Component({
  template: `
    <input required type=”text” formControlName=”username” />
  `
})
export class Login {
  username: FormControl
  constructor(){
    this.username = new FormControl('', Validators.required);
  }
}

FormGroup

Un FormGroup est un groupe de FormControl .

import {Component} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';

@Component({
  template: `
    <form novalidate [formGroup]=”form”>
      <input required type=”text” formControlName=”username” />
      <inputrequired type="email" formControlName=”email”/>
    </form>
  `
})
export class Login {
 form: FormGroup;
 
 constructor() {
   this.form = new FormGroup({
     username: new FormControl('', Validators.required),
     email: new FormControl('', Validators.required)
   });
 }
}

FormBuilder

import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';

@Component({
  selector: 'login',
  template: require('./login.html')
})
export class Login implements OnInit{
  loginForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.loginForm = this.createLoginForm();
  }

  private createLoginForm(): ControlGroup {
    return this.formBuilder.group({
      username: ['', Validators.compose([
        Validators.required,
        Validators.minlength(2),
        Validators.maxlength(15)
      ])],
      password: ['', Validators.compose([
        Validators.required,
        Validators.pattern('^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).*$'),
      ])]
    })
  }
}

Validation custom

export class ValidationService {

  static emailValidator(control) {
    // RFC 2822 compliant regex
    if (control.value.match(/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)) {
      return null;
    } else {
      return { 'invalidEmailAddress': true };
    }
  }
}
formBuilder.group({
  'email': ['', Validators.compose([
    Validators.required,
    ValidationService.emailValidator,
  ])]

Validation custom asynchrone

Il est egalement possible de définir une fonction de validation asynchrone.

 Au lieu de retouner un booléen , la fonction retournera une Promise

Nous le mettrons en place au chapitre suivant

Afficher les erreurs

<form [formGroup]="loginForm">
  <div [ngClass]="{'input-error': !loginForm.controls.email.valid}">
    <input placeholder="email" type="email" formControlName="email"/>
    <div *ngIf="loginForm.controls.email.errors?.email">
      email is invalid
    </div>
    <div *ngIf="loginForm.controls.email.errors?.required">
      required
    </div>
  </div>
</form>
.input-error {
  border: 1px solid red;
}

Surveiller les changements

export class Login implements OnInit{
  loginForm: FormGroup

  constructor(private formBuilder: FormBuilder){}

  ngOnInit():void {
    this.loginForm = this.createLoginForm()
    this.loginForm.valueChanges
      .subscribe(data => console.log('form changes', data.value));
  }
}

valueChanges est un Observable (voir chapitre suivant)

Http et RxJs

Introduction à la programmation réactive

Programmation fonctionnelle ?

  • Le concept a plus de 20 ans

  • Permet d'utiliser des fonctions comme "map" ou "filter" sur des traitements asynchrones

  • application réactive

Stream ?

  • Un stream est une séquence d'événement

  • Tout peut être un stream, du simple click à l'appel http

  • Application constituée uniquement de stream

  • Les streams peuvent se combiner

  • RxJS est le portage JavaScript de la bibliothèque Microsoft Reactive Extention, qui permet d'utiliser une chose appelée Observable.

RxJs

Angular2 n'impose pas l'usage de RxJS, mais l'utilise en interne (EventEmitter, Http)

Offre la possibilité d'une architecture "Model View Intent" (Andre Saltz)

Exemple avec un formulaire

<form [formGroup]="form" (ngSubmit)="onSubmit()">
   <p>
        <label>email</label>
        <input type="email" formControlName="email">
   </p>
</form>
this.form.valueChanges
  .map((value) => {
    value.email = value.email.toLowerCase();
    return value;
  })
  .filter((value) => this.form.valid)
  .subscribe(validValue => ...);

Observable

  • Nouveau type qui permet de s'abonner à des streams de données et de les manipuler

  • Pensez-y comme une API de manipulation de streams

  • Par convention les propriétés de type Observable sont suffixées par un "$"

  • La méthode subscribe permet de s'abonner à un stream (équivalent du then des promise, mais qui se déclenche plusieurs fois)

  • s'utilise conjointement avec un Observer

Subject BehaviourSubject

Ces deux classes heritent d'Observable et d'Observer

BehaviourSubject envoie a tout subscriber le dernier element émit, ainsi que toute la chaine d'element. 

Les opérateurs

  • Sortes de Pipe dans lesquels passent les données

  • Reprend les méthodes de Lodash mais pour des traitements asynchrones

var source$ = Rx.Observable.range(1,4); //1,2,3,4

//map (select) & flatMap (selectMany): changes each value 
//flatMap returns an observable so it works well with async operations
source$.map(x => x*2); //2, 4, 6, 8

//filter: returns only selected values based on custom logic
source$.filter(x => x % 2 === 0); //2, 4

//reduce: performs a computation on the stream and outputs the final value
source$.reduce((prev, curr) => prev + curr); //10

//scan: performs a computation on the stream but outputs intermittment values
source$.scan((prev, curr) => prev + curr); //1, 3, 6, 10

Http

import {Http} from '@angular/http';
import ...

@Injectable()
export class CommentService {
  private data: any[]
  constructor(private http: Http) {}

  loadComments() {
    this.http.get('http://localhost:3001/api/comments')
      .map((res:Response) => res.json()) //map to JSON
      .subscribe(
        data => { this.data = data}, 
        err => console.error(err)
      );
  }
}

RequestOptions, Headers, UrlSearchParams

import {Http,
        Headers, 
        RequestOptions, URLSearchParams} from '@angular/http'
import ...

@Injectable()
export class UserService {
  ...
  loadUsers() {
    const headers =  new Headers({'Content-Type': 'application/json'})
    let options = new RequestOptions({
      headers,
      search: new URLSearchParams('firstName=John&lastName=Smith}')
    })
    this.http.get('http://localhost:3001/api/users', options)
      .map((res:Response) => res.json()) //map to JSON
      ...
  }
}

Traitements parrallèles

import {Http} from '@angular/http';
import {Observable} from 'rxjs';
import ...

@Injectable()
export class PostService {
  ...
  loadPost(id: number) {
    return Observable.forkjoin(
      this.http.get(`http://localhost:3001/api/posts/${id}`)
        .map((res:Response) => res.json()),
      this.http.get(`http://localhost:3001/api/comments?postId=${id}`)
        .map((res:Response) => res.json()))
  }
}

Post, Put, Delete.....

import {Http} from '@angular/http';
import {Observable} from 'rxjs';
import ...

@Injectable()
export class PostService {
  ...
  createPost(post: IPost) {
    const body = JSON.stringify(post)
    return  this.http.post('/api/post', body)
      .map((res:Response) => res.json()))
  }
}

Retourner une Promise

import ...

@Component({
  template: `
    <div>
        <post-item *ngFor="let post of posts" [post]="post">
    </div>
  `
})
export class PostsComponent implements OnInit{
  ...
  constructor(private postService: PostService)
  ngOnInit) {
    this.postService
      .loadPosts()
      .toPromise()
      .then((data: any) => console.log(data))
      .catch((err: any) => console.error(err));
  }
}

La méthode next

import {BehaviourSubject} from 'rxjs/Rx'
import ...

@Injectable()
export class CommentService{
  data$: BehaviorSubject<any[]> = new BehaviourSubject<any[]>([])
  constructor(http: Http) {}

  loadComments() {
    this.http.get('http://localhost:3001/api/comments')
      .map((res:Response) => res.json())
      .subscribe(
        data => {this.data$.next(data)}, 
        err => console.error(err),
        () => console.log('done')
      );
  }
}

Le pipe async

import ...

@Component({
  template: `
    <div>
        <post-item *ngFor="#post in posts | async" [post]="post">
    </div>
  `
})
export class PostsComponent implements OnInit{
  ...
  constructor(private postService: PostService)
  ngOnInit() {
    this.posts = postService.data$
    this.postService.loadPosts()
  }
}

Un loader dans le component

import ...

@Component({
  template: `
    <div>
        <span *ngIf="loading$ | async">Loading...</span>
        <post-item *ngFor="let post of posts$ | async" [post]="post">
    </div>
  `
})
export class PostsComponent implements OnInit{
  loading$: Subject<boolean> = new Subject<boolean>();
  posts$: Observable<any[]>;
  constructor(private postService: PostService) {}
  ngOnInit() {
    this.loading$.next(true);
    this.posts$ = this.postService
      .loadPosts()
      .do(() => this.loading$.next(false))
  }
}

Questions ?

Angular 2

By AdapTeach

Angular 2

  • 2,496