Charles Jacquin
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
...
Dernière version finallisé ECMAScript6 (ES6, ES2015, Harmony)
Changement de convention de nommage (ES7 = ES2016)
Implémentation en cours dans les browsers
Utilisation de transpileurs et de polyfills pour assurer la compatibilité
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
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)
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
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
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');
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);
}
}
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
// 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
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:
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;
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):
}
const name = 'white';
const myOldSchoolString = 'yo Mr ' + name;
const myEs2015String = `yo Mr ${name}`;
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 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 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
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.
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))
Traitements parrallèles
Promise.all([
myPromisedTimer(4),
myPromisedTimer(8),
anotherPromise()
])
.then(response => {
//response is an array of 3
})
.catch(err => console.error(err))
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()
}
}
Méthode statique
Client.generateGuid()
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
Héritage
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...
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"
}
window.onmousedown = (mouseEvent) => {
console.log(mouseEvent.button); // <- Error
};
window.onmousedown = (mouseEvent: any) => {
console.log(mouseEvent.button); // <- OK
};
TypeScript
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");
}
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
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)
propriété optionnelle ('?')
interface LabelledValue {
label: string;
size?: number;
}
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;
}
}
Pour définir une signature de fonction...
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 :
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;
}
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.
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
"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"
}
module bundler.
The
production
unbiased
flexible
extensible
open source
Webpack va prendre en charge le build et le développement de A à Z.
$ 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"
}
}
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' },
]
}
});
$ npm i typescript -g
$ npm i typings -g
Vous devrez parfois définir des interfaces pour utiliser certaines librairies
$ typings install lodash --save
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
<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
Custom Elements
Templates
Imports
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
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>"
})
];
import {Component} from '@angular/core';
@Component({
selector: 'my-app',
template: `<main>
<h1> This is my first Angular2 app</h1>
</main>`
})
class MyAppComponent {
}
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 { }
// 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);
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>'
}
RootComp
Child1Comp
Child2Comp
[property]="expression"
(event)="update()"
@Input()
@Output()
<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()
import {Component, Input} from '@angular/core'
@Component({
selector: 'greeter',
template: `<h1>Hello, {{ name }}</h1>`
})
export class Greeter {
@Input() name: string;
}
<greeter [name]="'John'"></greeter>
<input (event)="expression" />
Accès à tous les évènements natifs du DOM
Possibilité de définir de nouveaux événements
@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>
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);
}
}
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>
Nécessité d'importer les composants utilisés dans le Template
Propriété declarations de @Component
Erreur si composants non utilisés
@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.
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()
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)
@Component({
...
encapsulation?: ViewEncapsulation,
...
})
class UserItem { ... }
//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
}
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 {
}
<p [my-directive]="data">Results may vary</p>
<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>
<input type="color" #colorInput />
<input type="number" #fontSize />
<div [ngStyle]="{color: colorInput.value}"
[style.font-size.px]="fontSize">
<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>
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
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']
}
}
<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>
<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>
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;
}
}
{{ collectionOfUsers | orderBy:'firstName' | limitTo:5 }}
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
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'uppercase'
})
export class UpperCasePipe implements PipeTransform {
transform(value: String, args: any[]) {
return value.toUpperCase();
}
}
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{}
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...');
}
}
Injection de dépendances
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
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();
}
}
@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();
}
}
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]
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 {
}
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
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>
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>
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]
}
]
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}}
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [ FormsModule ],
...
})
...
<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>
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [ ReactiveFormsModule ],
...
})
...
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);
}
}
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)
});
}
}
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).*$'),
])]
})
}
}
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,
])]
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
<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;
}
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)
Introduction à la programmation réactive
Le concept a plus de 20 ans
Permet d'utiliser des fonctions comme "map" ou "filter" sur des traitements asynchrones
application réactive
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.
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)
<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 => ...);
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
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.
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
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)
);
}
}
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
...
}
}
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()))
}
}
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()))
}
}
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));
}
}
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')
);
}
}
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()
}
}
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))
}
}
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
D'aprés la documentation :
"Redux is a predictable state container for JavaScript apps"
$ npm install --save redux
const ADD_TODO = 'ADD_TODO'
{
type: ADD_TODO,
text: 'Create example action for talk'
}
// a typical redux action
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.
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
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,
});
Implémentation de redux utilisant rxjs et prévu pour angular2
npm i @ngrx/core @ngrx/store @ngrx/effect -S
4 bibliothèques :
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 {}
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 });
}
}
Questions ?