Mickael Jeanroy
mickael.jeanroy@zenika.com
INSA LYON - IF 2014
19 / 11 / 2014
Programme
Javascript
Ecosystème & Best practice
Angular.js
TP
Build your own Angular.js
Du code, beaucoup de code !
Javascript
Introduction
- Créé en 1995 par Brendan Eich pour Netscape
-
Standardisé: ECMAScript
- ECMAScript 3 : 1999
- ECMAScript 5 : 2009
- ECMAScript 6 : encore en cours de développement !
- ECMAScript 5 est la version actuellement implémentée dans les navigateurs (Chrome, Firefox, Safari, IE10)
- Souvent considéré comme un langage "sale", pas maintenable etc.
- On va voir aujourd'hui qu'en connaissant ses faiblesses, il devient au contraire un langage très puissant.
Javascript
Introduction
- Langage de script
-
S'exécute dans une VM (Virtual Machine)
- Browser: V8 (Chrome) ; SpiderMonkey (Firefox) ; JavaScriptCore (Safari) etc.
- Serveur: V8 (NodeJS) ; Rhino (Java) etc.
- La mémoire est gérée grâce à un garbage collector
- Langage dynamique
- Langage faiblement typé
- Langage orienté prototype
Javascript
Debug
- Le plus basique: console.log
- Permet de laisser des traces sur les appels
- Attention: certaines vieilles versions d'IE ne le supportent pas
- Chrome ajoute certaines features: console.table() / console.time() / console.memory etc.
// Fonction nommée "foo"
function foo() {
console.log('hello world', 'hello bar');
}
Javascript
Debug
-
Chrome Dev Tools : le meilleur !
- Firebug
- Safari Web Inspector
- Opera DragonFlyx
- IE : présent depuis IE9
Javascript
Un langage faiblement typé
- Le mot clé "var" permet de déclarer une variable
- Peu de type: number, string, boolean, function, null
- Une variable sans aucune valeur est "undefined"
- Les objets et les tableaux permettent de les composer
- Une variable n'a pas de type fixe (typage dynamique)
var foo;
console.log(foo); // 'undefined'
console.log(typeof foo); // 'undefined'
foo = 5;
console.log(typeof foo); // 'number'
foo = 'string';
console.log(typeof foo); // 'string'
foo = true;
console.log(typeof foo); // 'boolean'
Javascript
Les tableaux
- Une seule structure de données: les tableaux
- Suite ordonnée d'éléments, chaque élément étant accessible par son index
- La propriété "length" donne la taille du tableau
- ECMAScript 6 apporte de nouvelles structures de données: Set, WeakSet, Map, WeakMap
var foo = [1, 2, 3];
console.log(foo); // [1, 2, 3]
foo.push('string', true);
console.log(foo); // [1, 2, 3, 'string', true]
console.log(foo.length); // 5
console.log(foo[4]); // true
console.log(foo[5]); // undefined
Javascript
Les objets
- En javascript, un objet est dynamique: on peut lui rajouter un attribut au runtime
- Ni plus, ni moins qu'une map clé valeur
var foo = {};
console.log(typeof foo); // 'object'
console.log(foo); // {}
foo = {
id: 1
};
console.log(foo); // {id: 1}
foo.name = 'Mickael';
console.log(foo); // {id: 1, name: 'Mickael'}
foo.skills = ['Js', 'Java'];
console.log(foo.skills); // ['Js', 'Java']
console.log(foo['skills']); // ['Js', 'Java']
Javascript
Les objets
- Il est possible d'itérer simplement sur tous les attributs d'un objet
var foo = {
id: 1,
name: 'foo'
};
for (var key in foo) {
console.log(key + ' = ' + foo[key]);
}
// id = 1
// name = foo
Javascript
Spécificités
-
En javascript, on distingue égalité et égalité stricte
- L'opérateur "==" (négation: "!=") permet de comparer deux objets indépendamment du type
- L'opérateur "===" (négation: "!==") permet de comparer deux objets en prenant en compte les types
- Pour le cas des objets et des tableaux, c'est toujours une comparaison d'instance qui est faite!
var foo = 1;
var bar = '1';
console.log(foo == bar); // true
console.log(foo === bar); // false
console.log([1, 2, 3] == [1, 2, 3]); // false
console.log([1, 2, 3] === [1, 2, 3]); // false
Javascript
Spécificités
- Question: qu'affiche ce code ?
console.log('' == '0'); // ???
console.log('' == 0); // ???
console.log('0' == 0); // ???
Javascript
Spécificités
WTF ??
console.log('' == '0'); // false
console.log('' == 0); // true
console.log('0' == 0); // true
Javascript
Spécificités
http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.1
Javascript
Spécificités
- Pour simplifier: Javascript essaye de rapprocher les variables vers deux types identiques.
- Cela peut amener à quelques spécificités un peu "étranges".
- Simplifiez-vous la vie, utilisez toujours l'égalité stricte !
Javascript
Un langage orienté fonctionnel
- En javascript, les fonctions sont partout
- Considéré comme un type comme un autre: le type "function" est un type comme un autre
- Une fonction peut être nommée ou anonyme
// Fonction nommée "foo"
function foo() {
console.log('hello world');
}
foo();
// Fonction anonyme qu'on affecte à une variable "foo"
var foo = function () {
console.log('hello world');
};
foo();
Javascript
Un langage orienté fonctionnel
- Une fonction a un nombre de paramètre variable
- Le mot clé "arguments" au sein d'une fonction permet de récupérer la liste des paramètres
- Une fonction peut ne rien retourner
// Fonction nommée "foo"
function foo(param1, param2) {
console.log(param1, param2, 'arguments = ', arguments);
if (param1 === 1) {
return true;
}
}
foo(); // undefined, undefinfed, arguments = []
foo(1); // 1, undefinfed, arguments = [1]
foo(1, 2); // 1, 2, arguments = [1, 2]
foo(1, 2, 3); // 1, 2, arguments = [1, 2, 3]
Javascript
Un langage orienté fonctionnel
- Une fonction peut être donnée en paramètre à une autre fonction
- Une fonction peut retourner une autre fonction
- En langage fonctionnel, de telles fonctions sont appelées fonctions d'ordre supérieur
- Une fonction peut également être affectée à un attribut d'un objet
- Avec Angular.js, on manipule et on écrit des fonctions tout le temps
Javascript
Un langage orienté fonctionnel
// Fonction nommée "foo": cette fonction prend en paramètre une fonction
// à exécuter
function foo(func) {
func();
}
// Ce code donne une fonction anonyme en paramètre de la fonction "foo"
foo(function () {
console.log('hello world');
});
// Ce code déclare une fonction nommée "myFunc"
// Cette fonction est ensuite donnée en paramètre de la fonction "foo"
function myFunc() {
console.log('hello world');
}
foo(myFunc);
Javascript
Un langage orienté fonctionnel
// Fonction nommée "foo"
// Cette fonction renvoie une fonction permettant de "logguer" le
// contenu d'un tableau lorsqu'elle est exécutée
function foo(array) {
return function () {
for (var i = 0; i < array.length; i++) {
console.log(array[i]);
}
};
}
var displayArray1 = foo([1, 2, 3]);
var displayArray2 = foo(['foo', 'bar']);
displayArray1(); // 1, 2, 3
displayArray2(); // 'foo', 'bar'
Javascript
Un langage orienté fonctionnel
// On déclare un objet avec une fonction comme attribut
var batman = {
name: 'Batman',
speak: function () {
console.log('I am Batman');
}
};
console.log(batman); // {name: "Batman", speak: function}
console.log(typeof batman.speak); // 'function'
// Exécution de la fonction
batman.speak(); // 'I am Batman'
Javascript
Un langage orienté fonctionnel
Comme beaucoup de langages fonctionnels, ECMAScript 5 rajoute des fonctions de manipulation de listes: forEach, map, some, every, reduce etc.
// Exécute une fonction sur chaque élément d'un tableau
[1, 2, 3].forEach(function (current, idx) {
console.log(current, idx);
});
// Applique une transformation à chaque élément et retourne
// un nouveau tableau contenant tous les résultas
// Ex: Multiplication par deux de tous les éléments
var newArray = [1, 2, 3].map(function (current) {
return current * 2;
});
console.log(newArray); // 2, 4, 6
Javascript
Un langage orienté fonctionnel
// Vérifie une condition sur chaque élément d'un tableau
// Est-ce que tous les éléments sont pairs ?
[2, 4, 6].every(function (current) {
return current % 2 === 0;
});
// Vérifie qu'une condition est vérifié sur au moins un élément d'un tableau
// Est-ce qu'au moins un élément est pair ?
[1, 2, 3].some(function (current) {
return current % 2 === 0;
});
// Réduit le contenu du tableau à une valeur
// Ex: somme de tous les éléments du tableau
var reduceValue = [1, 2, 3].reduce(function (memo, current) {
return memo += current;
}, 0);
console.log(reduceValue); // 6
Javascript
Un langage orienté prototype
- Le langage Javascript n'est pas un langage orienté objet
- Mais, cela ne signifie pas qu'on ne peut utiliser des objets !
- On peut déclarer des constructeurs, la forme la plus classique est d'instantier une fonction avec le mot clé "new"
// Fonction nommée "foo"
var Hero = function (name) {
this.name = name;
this.speak = function () {
console.log('I am ' + this.name);
};
};
var batman = new Hero('Batman');
var superman = new Hero('Superman');
batman.speak(); // I am Batman
superman.speak(); // I am Superman
console.log(batman.speak === superman.speak); // false
Javascript
Un langage orienté prototype
- Chaque objet dispose d'un "prototype"
- Un prototype n'est rien de plus qu'une liste de propriétés (a.k.a un objet) partagé par toutes les instances d'un même type
var Hero = function (name) {
this.name = name;
};
Hero.prototype = {
speak: function () {
console.log('I am ' + this.name);
}
};
var batman = new Hero('Batman'); // I am Batman
var superman = new Hero('Superman'); // I am Superman
console.log(batman.speak === superman.speak); // true
Javascript
Un langage orienté prototype
- ECMAScript 5 introduit la fonction Object.create pour créer un objet en lui donnant son "prototype"
var prototype = {
speak: function () {
console.log('I am ' + this.name);
}
};
var batman = Object.create(prototype);
batman.name = 'Batman';
var superman = Object.create(prototype);
superman.name = 'Superman';
Javascript
Un langage orienté prototype
-
Le langage Javascript ne possède pas de "classe" au sens orienté objet.
- Pas d'héritage
- Pas de visibilité "private" ou "protected"
- ECMAScript 6 introduit la notion de classes, mais reste limité à ce qu'on vient de voir (pas de visibilité, etc.)
Ecosystème
Bonnes pratiques
-
Pendant longtemps, le Javascript est resté très "artisanal"
- Pas de tests automatisés
- Pas d'outils pour récupérer automatiquement ses librairies
- Depuis quelques années, la communauté a développé tous les outils nécessaires !
- Ces outils sont basés sur node.js
Ecosystème
Single Page Application
Wikipédia: Une application web monopage (en anglais single-page application ou SPA) est une application web accessible via une page web unique. Le but est d'éviter le chargement d'une nouvelle page à chaque action demandée, et de fluidifier ainsi l'expérience utilisateur.
Ecosystème
Single Page Application
- Dans une Single Page Application (SPA), la page html n'est jamais rechargée entièrement
- Seul une partie de la page, le contenu dynamique, est mis à jour
-
Exemples
Ecosystème
Single Page Application
Ecosystème
Single Page Application
Problème: la gestion de l'historique !
Ecosystème
Single Page Application
-
Solutions:
- Pendant longtemps, la solution a été d'utiliser le contenu situé aptès le caractère # (hash) : c'est la solution utilisée par Gmail
- HTML5 standardise ce concept avec l'API push state : c'est la solution utilisée par Facebook
- L'API push state est à préférer car c'est un standard et facilite l'indexation par les moteurs de recherches
Ecosystème
REST
- REpresentational State Transfer
- Standard d'échange entre un client et un serveur
- Style d'architecture
- Indépendant du protocole (http, etc.)
- Très rare de l'utiliser sur un autre protocole que http
Ecosystème
REST
Doit respecter ces contraintes:
- Client / Serveur
- Stateless
- Les échanges peuvent être mis en cache
- Identification de resources
Ecosystème
REST
Exemple: HTTP
- Client / Serveur : OK par définition
- Stateless : OK par définition, même si ça peut être compliqué à cause des sessions
- Les échanges peuvent être mis en cach : OK via les headers HTTP
- Identification de resources : OK via les URL
Ecosystème
REST
Exemple: Api Tweeter
- GET /tweets
- POST /tweets
- PUT /tweets
- DELETE /tweets
Ecosystème
Bonnes pratiques
-
Avant de livrer une application en production, il faut la "builder"
- Concaténer ses fichiers javascript en un seul fichier
- Minifier son code Javascript
- Gzipper ses fichiers
- Jouer les tests unitaires pour s'assurer de la stabilité du code
- Le but est de garantir la non régression, de réduire le poids de la page, et donc améliorer l'expérience utilisateur !
- Necessité d'automatiser ces tâches
Ecosystème
Test unitaires
Angular.js
by Google
- Angular.js est un framework javascript "full stack"
- Développé par Google (Miško Hevery, Igor Minar, Vojta Jina, etc.)
- Version 1.3.2
- Open source: https://github.com/angular/angular.js
Angular.js
Introduction
- Data binding
- Single Page Application (SPA)
- REST
- Dependency Injection (DI)
- MVC
Angular.js
Introduction
DEMO
Angular.js
Composants
- Module
- Contrôleurs
- Formulaires
- Services et factory
- Filtres
- Directives
- Routeur
Angular.js
Module
- Contient les différents composants de votre application
- Toute application contient au moins un module
- On le déclare dans le code (avec l'attribut ng-app) et dans un fichier Javascript
<html ng-app="myApp">
<head>
</head>
<body>
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="myApp.js"></script>
</body>
</html>
// Création d'un module dans myApp.js
var myAppModule = angular.module('myApp', []);
Angular.js
Module
- Equivalent d'un "main" (point d'entrée de votre application)
- Tous les composants de votre application seront créés au sein de votre module
- Le deuxième paramètre est la liste des modules dont votre application dépend
- Pour récupérer un module sans le créer, il suffit de ne pas préciser le second paramètre
// Création d'un module
var myAppModule = angular.module('myApp', []);
// Récupération du module
var module = angular.module('myApp');
console.log(myAppModule === module); // true
Angular.js
Contrôleurs
- Un contrôleur contient la logique (le comportement) associée à votre page HTML (qui représente la vue du modèle MVC)
- C'est un contrôleur conforme au pattern MVC
- Un contrôleur angular.js n'est rien d'autre qu'une fonction !
<div ng-controller="myController">
<p>Ma page HTML</p>
</div>
angular.module('myApp')
.controller('myController', function () {
// Le code de votre contrôleur !
});
Angular.js
Contrôleurs
- Pour afficher des données gérées dans votre contrôleur, il faut les partager à la vue
- Pour partager des données, on utilise l'objet $scope
- C'est le M du modèle MVC
<div ng-controller="myController">
<p>Hello {{ name }}</p>
</div>
angular.module('myApp')
.controller('myController', ['$scope', function ($scope) {
$scope.name = 'world';
}]);
Angular.js
Contrôleurs
- L'objet $scope a été instancié par Angular.js : notre application ne crée pas d'objets, mais demande à Angular.js de nous les fournir
- C'est l'injection de dépendance
-
C'est un concept qu'on retrouve dans beaucoup d'autres langages et frameworks
- Java: Spring, CDI
- PHP: Symfony
Angular.js
$scope
-
L'objet $scope est un objet très important
- Il contient les données accessible depuis la page HTML
- Il hérite (au sens héritage prototypal) de ses scopes parents
- Il possède des méthodes utiles: $watch ; $on
- Tout objet $scope hérite forcément du $rootScope
- Toute application Angular.js possède un (et un seul) $rootscope
Angular.js
$scope
Chaque $scope hérite de son scope parent (défini par l'arborescence du dom)
<div ng-controller="myController">
<div ng-controller="myOtherContoller">
</div>
</div>
angular.module('myApp')
.controller('myController', ['$scope', function ($scope) {
$scope.name = 'world';
}]);
angular.module('myApp')
.controller('myOtherController', ['$scope', function ($scope) {
console.log($scope.name); // world
}]);
Angular.js
$scope
- Pour afficher les valeurs du scope, le plus simple est d'utiliser les "doubles moustaches"
- Une autre façon est d'utiliser la directive ng-bind
<div ng-controller="myController">
Hello {{name}}
Hello <span ng-bind="name"></span>
</div>
angular.module('myApp')
.controller('myController', ['$scope', function ($scope) {
$scope.name = 'world';
}]);
Angular.js
$scope
- La méthode $watch permet d'exécuter du code quand une valeur change
- Ressemble fortement à Object.observe
angular.module('myApp')
.controller('myController', ['$scope', function ($scope) {
$scope.name = 'world';
$scope.$watch('name', function (newValue, oldValue) {
console.log(newValue, oldValue);
});
}]);
Angular.js
$scope
- La méthode $on permet d'écouter un évenement provenant d'un autre contrôleur
- Permet de "dispatcher" des événements
- Une façon de faire communiquer des contrôleurs (mais pas toujours la bonne façon)
angular.module('myApp')
.controller('myController', ['$scope', '$rootScope',
function ($scope, $rootScope) {
$scope.$on('myEvent', function (event, param1) {
console.log(param1);
});
$rootScope.$broadcast('myEvent', 'foo');
}
]);
Angular.js
Formulaires & ng-model
- Pour récupérer les valeurs saisies dans un formulaire, on utilisera l'attribut ng-model
- Au fur et à mesure de la saisie, les variables du scope sont mises à jour par Angular.js
- Si la valeur est mise à jour programmatiquement, la valeur du champ est mise à jour
- C'est ce qu'on appelle le double binding
Angular.js
Formulaires & ng-model
Exemple
<div ng-controller="myController">
<form>
<input type="text" ng-model="name">
Message: {{ name }}
</form>
</div>
angular.module('myApp')
.controller('myController', ['$scope', function ($scope) {
$scope.name = 'hello world';
$scope.$watch('name', function (newValue) {
console.log(newValue);
}):
}]);
Angular.js
Formulaires & ng-model
La directive ng-model est l'une des features les plus importantes d'Angular.js
<div ng-controller="myController">
<input id="my-input" type="text" ng-model="name">
</div>
VS
var myValue = 'foo';
$('#my-input').on('keyup', function (e) {
myValue = $(this).val();
});
myValue = 'bar';
$('#my-input').val(myValue);
Angular.js
Http
- Angular.js fournit une api pour faire des requêtes HTTP
- S'interface parfaitement avec une api REST
- Prend en charge toutes les spécificités des navigateurs
- Il suffit d'injecter le service $http dans un contrôleur
-
Permet de faire toutes les requêtes "classique"
Angular.js
Http
Exemple : GET "/foo"
angular.module('myApp')
.controller('myController', ['$scope', '$http',
function ($scope, $http) {
$http.get('/foo')
.success(function (data) {
$scope.data = data;
})
.error(function (error) {
$scope.error = error;
});
}
]);
Angular.js
Http
-
Attention, le résultat est asynchrone
- Communication synchrone: vous demandez un résultat et l'avez "tout de suite" (i.e vous êtes bloqué tant que la réponse n'est pas là)
- Communication asynchrone: vous demandez un résultat, mais vous pouvez passez à autre chose et vous serez averti de la réponse une fois disponible
- On récupère le résultat via un callback de succès
- On récupère l'erreur via un callback d'erreur
Angular.js
Factory
- Une factory permet d'extraire des composants ré-utilisables
- On peut le récupérer dans un contrôleur par injection de dépendance
- C'est à la factory d'instancier le composant
angular.module('myApp')
.factory('myComponent', function () {
return {
helloWorld: function () {
console.log('hello world');
}
};
})
.controller('myController', ['$scope', 'myComponent',
function ($scope, myComponent) {
$scope.message = myComponent.helloWorld();
}
]);
Angular.js
Factory
- La factory est exécutée la première fois où le composant est injecté
- Le composant ne sera créé qu'une et une seule fois (singleton): idéal pour partager des données entre contrôleurs !
- Idéal pour factoriser du code entre contrôleurs
Angular.js
Service
- Un service ressemble à une factory avec une différence : l'objet est déjà instancié
- On définit juste les attributs sur l'objet "this" (qui représente l'objet instancié)
- Peut être vu comme un raccourci pour écrire une factory
angular.module('myApp');
.service('myComponent', function () {
this.helloWorld = function () {
return 'Hello World';
};
})
.controller('myController', ['$scope', 'myComponent',
function ($scope, myComponent) {
$scope.message = myComponent.helloWorld();
}
]);
Angular.js
Filtres
- Un filtre est un composant permettant d'altérer l'affichage d'une valeur
- Evalué lorsque le DOM est affiché / rafraichi (à chaque fois !)
- Permet de bien séparer l'affichage de la manipulation des données
- Un exemple est l'utilisation du filtre angular "date"
<div ng-conroller="myController">
<Current date: {{ myDate | date:'dd/MM/yyyy' }}>
</div>
angular.module('myApp')
.controller('myController', ['$scope', function ($scope) {
$scope.myDate = new Date();
}]);
Angular.js
Filtres
-
Angular fournit une collection de filtres prêts à l'emploi
- date
- number
- currency
- uppercase / lowercase
- orderBy
- etc.
- La documentation est très bien faite !
Angular.js
Filtres
- Un filtre peut également être utilisé dans un contrôleur
<div ng-conroller="myController">
Current date: {{ myDate }}>
</div>
angular.module('myApp')
.controller('myController', ['$scope', '$filter',
function ($scope, $filter) {
$scope.myDate = $filter('date')(new Date(), 'dd/MM/yyyy');
}
]);
Angular.js
Filtres
- Les filtres peuvent se chaîner (comme un pipe unix)
<div ng-conroller="myController">
Current date: {{ myDate | date:'dd/MMMMM/yyyy' | uppercase }}>
</div>
angular.module('myApp')
.controller('myController', ['$scope', function ($scope) {
$scope.myDate = new Date();
}]);
Angular.js
Filtres
- Ecrire son propre filtre consiste "juste" à écrire une fonction !
<div ng-conroller="myController">
foo filter {{ myData | foo }}>
</div>
angular.module('myApp')
.controller('myController', function ($scope) {
$scope.myData = 'hello';
})
.filter('foo', function () {
// On retourne une fonction qui sera exécutée à chaque rafraichissement
return function (param) {
return param + ' foo';
};
});
Angular.js
Directives
- Les directives représentent une extention du DOM
- Peut être utilisée comme un attribut ou une nouvelle balise
- Les attributs ng-controller / ng-app / ng-model sont des directives !
Angular.js
Directives
module.directive('ngBind', function () {
return function (scope, element, attrs) {
scope.$watch(attrs.ngBind, function (newValue) {
element.html(newValue);
});
};
});
Angular.js
Directives
- Sujet vaste et compliqué qu'on n'abordera pas plus aujourd'hui
- Nombreux exemples sur Github (angular-ui, angular-strap)
- Beaucoup de directives Angular.js (ngBind, ngModel, etc.) sont simples à lire et sont très instructives
Angular.js
Router
- Le routeur Angular.js est un composant très simple
- Il suffit de définir les routes disponibles et les templates html associés
- La page doit contenir un élément avec un attribut ng-view
- C'est ce bloc qui sera mis à jour à chaque changement de route
Angular.js
Best practices
- Pas de manipulation du DOM dans un contrôleur : c'est le rôle d'une directive !
- La customisation de l'affichage doit être fait dans un filtre
- Gardez vos contrôleurs simple et "petit"
- Utiliser des services pour factoriser du code et extraire le code "compliqué" des contrôleurs
-
Faites des tests unitaires : jasmine + karma = :)
Angular.js
Exercices
- Récupérer le zip du tp
- Lancer le serveur en lançant la commande "node server/app.js"
- Enregistrer son login
- Valider avec moi l'exercice
- Récupérer le mot de passe de validation !
Angular.js
Exercices
Récupérer les tweets en faisant un GET sur "/tweets"
Brancher le post d'un tweet
Ecrire un filtre pour afficher le login précédé du caractère '@'
Capter l'événement 'tweet:new' pour afficher les tweets en temps réel
Refactorer l'affichage du tweet en utilisant une directive