Angular 2
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é
-
Implémentation en cours dans les browsers
-
Utilisation de transpileurs et de polyfills pour assurer la compatibilité
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 ClientService;
ES6
import ClientService from './ClientService';
ES6
import MyService from './ClientService';
class ClientComponent {
myService = new MyService();
submitClient(client) {
this.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 = 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 = (mouseEvent) => {
console.log(mouseEvent.button); // <- Error
};
window.onmousedown = (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
-
Generateur de code
-
build
-
test (unit, e2e)
-
standard
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()
@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é declarations de @Component
-
Erreur si composants non utilisés
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]
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
Guards
Permet de restreindre l'accés à une partie de l'application.
Ou de recuperer de la data avant d'afficher le component
Peut être synchrone ou asynchrone
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate() {
console.log('AuthGuard#canActivate called');
return true;
}
}
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard]
}
]
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 [hidden]="!loginForm.controls.email.errors?.email">
email is invalid
</div>
<div [hidden]="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))
}
}
Angular2 + rxjs + redux
=
ngRx
I wrote Redux while working on my React Europe talk called “Hot Reloading with Time Travel”. My goal was to create a state management library with minimal API but completely predictable behavior, so it is possible to implement logging, hot reloading, time travel, universal apps, record and replay, without any buy-in from the developer.
Dan Abramov
Redux
D'aprés la documentation :
"Redux is a predictable state container for JavaScript apps"
- Facile à comprendre
- Cohérent
- Uniquement les données et la manière de les modifier
- Rien à voir avec le rendu des données (vue)
Redux ?
$ npm install --save redux
plugin debug pour chrome
- reducer = (state, action) => state
const ADD_TODO = 'ADD_TODO'
{
type: ADD_TODO,
text: 'Create example action for talk'
}
// a typical redux action
3 Principes de redux
Single Source of Truth
L'état de toute votre application est stocké dans un arbre d'objets se trouvant lui même dans un store unique.
State is read-only
L'état de votre application est "immutable", la seule manière de le modifier est d’émettre une action (dispatching).
Changes are made with pure functions
L'état sera modifié par des "pure functions", ces fonctions doivent toujours retourner le même output pour un même input.
Architecture
Unidirectional Data Flow
Reducer
Redux passe les actions a travers le "reducer" et fournit le nouveaux state à la vue.
Le reducer peut être divisés en plusieurs reducers spécialisés
Un store unique
Single Source of Truth ?
- Facilite le debug
- Les données venant du serveur n'ont pas à se trouver dans plusieurs endroits
State is read-only ?
- La vue ou les requêtes http ne peuvent modifier directement le state
- Toutes les mutations du state sont centralisées et se produisent une par une dans un/des reducer/s
- Les actions décrivent explicitement ce qui se passe
- Les actions sont des "plain old object" qui peuvent être serialisés, loggués ou utilisés pour le debug
- La librairie immutable.js peut vous être utile, ou son équivalent moderne seamless-immutable.
Changes are made with pure functions ?
- une fonction "pure" retournera toujours le même output pour un input donné, ce qui évite/interdit les effets de bords indésirables
- Composable
- combineReducers({filters: filtersReducer, list: listReducer})
- Réutilisable
- reducer spécialisé pour les formulaires par exemple
Un peu de code
reducer/counter.js
export const counter = (state = 0, action) => {
switch(action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
case default:
return state;
}
}
reducer/index.js
import { combineReducers } from 'redux';
import counter from './todos';
export default combineReducers({
counter,
});
ngRx
Implémentation de redux utilisant rxjs et prévu pour angular2
npm i @ngrx/core @ngrx/store @ngrx/effect -S
4 bibliothèques :
- Store
- Effect
- DevTools
- RouterStore
Store
import { ActionReducer, Action } from '@ngrx/store';
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const RESET = 'RESET';
export const counterReducer: ActionReducer<number> = (state: number = 0, action: Action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
case RESET:
return 0;
default:
return state;
}
}
import { NgModule } from '@angular/core'
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter';
@NgModule({
imports: [
BrowserModule,
StoreModule.provideStore({ counter: counterReducer })
]
})
export class AppModule {}
Dans le module
import { Store } from '@ngrx/store';
import { INCREMENT, DECREMENT, RESET } from './counter';
interface AppState {
counter: number;
}
@Component({
selector: 'my-app',
template: `
<button (click)="increment()">Increment</button>
<div>Current Count: {{ counter | async }}</div>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset Counter</button>
`
})
class MyAppComponent {
counter: Observable<number>;
constructor(private store: Store<AppState>){
this.counter = store.select('counter');
}
increment(){
this.store.dispatch({ type: INCREMENT });
}
decrement(){
this.store.dispatch({ type: DECREMENT });
}
reset(){
this.store.dispatch({ type: RESET });
}
}
Au niveau du component
Questions ?
Angular 2
By AdapTeach
Angular 2
- 1,301