Attribut par scope - @
angular.module('app', [])
.directive('myShadow',myShadowDirective);
function myShadowDirective(){
return {
restrict: 'A',
scope: {
shadowColor: '@'
},
link: function(scope,element,attrs){
attrs.$observe('shadowColor',function(){
element.css('box-shadow','4px 4px 4px '+scope.shadowColor);
})
}
}
}
<section ng-app="app">
<p my-shadow shadow-color="blue">
lorem ipsum...
</p>
</section>
http://codepen.io/Ambient-IT/pen/EjvaoE?editors=101
Attribut par scope (=)
angular.module('app', []) <section ng-app="app" ng-controller="DemoController as ctrl">
.controller('DemoController',DemoController) <p my-shadow shadow-config="ctrl.shadowConfig">
.directive('myShadow',myShadowDirective); lorem ipsum
</p>
function DemoController(){
this.shadowConfig = { </section>
width: 2,
height: 2,
deep: 2,
color: '#d01616'
}
}
function myShadowDirective(){
return {
restrict: 'A',
scope: {
shadowConfig: '='
},
link: function(scope,element,attrs){
attrs.$observe('shadowConfig',function(){
var conf = scope.shadowConfig;
var shadowStr = conf.width+'px '+conf.height+'px '+conf.deep+'px '+conf.color;
element.css('box-shadow',shadowStr);
})
}
}
}
http://codepen.io/Ambient-IT/pen/JdJQVr?editors=101
Attribut par scope (&)
angular.module('app',[])
.directive('demo',demoDirective)
.controller('DemoController',DemoController);
function demoDirective(){
return {
template: '<div><span>Hello </span>'+
'<span ng-click="demoCtrl.worldClicked()">World</span>'+
'</div>',
scope:{},
controller: function(){
var self = this;
this.worldClicked = function(){
self.demoCtrl.clicked('hello !!!');
}
},
controllerAs: 'demoCtrl',
bindToCtrl: {
clicked: '&'
}
}
}
function DemoController(){
this.direciveClicked = function(message){
alert(message);
}
}
<section ng-app="app" ng-controller="DemoController as ctrl">
<div demo clicked="ctrl.directiveClicked"></div>
</section>
http://codepen.io/Ambient-IT/pen/jPLEeB?editors=101
Transclusion
angular.module('app',[])
.directive('demo',demoDirective);
function demoDirective(){
return {
transclude: true,
template: '<article><p>Hello </p>'+
'<p ng-transclude></p>'+
'</article>'
}
}
La transclusion permet d'insérer un peu de HTML à
un endroit précis du template de la directive
<section ng-app="app">
<h1>Demo !!!!</h1>
<demo>
<div>
I will be included in ng-transclude DOM's node
</div>
</demo>
</section>
Intéraction entre directives
Afin de créer des composants complexes, il est
possible d' imposer l'utilisation conjointe de
plusieurs directives.
Il faut pour cela utiliser l'attribut require dans
la definition d'une directive.
angular.module('app',[]) <section ng-app="app">
.directive('demo',demoDirective); <demo ng-model="test.value">
function demoDirective(){ </demo>
return { </section>
restrict: 'E',
require: 'ngModel',
link: function(scope,element,attrs,ngModelCtrl){
//we can manipulate ngModel's value
}
}
}
$observe
$observe fonctionne de la
même manière que $watch.
$observe n'est utilisable que dans une
directive, en effet $observe ne peut être
utilisé qu'avec le DOM.
Il faut toujours privilégier l'utilisation de $observe par rapport à
scope.$watch dans une directive.
Validation de formulaire personnalisée
Depuis la version 1.3, il est possible de créer ses
propres règles de validation .
$validators
$validators est un nouvel attribut de ngModel qui permet
d'ajouter un ou plusieurs validateurs "custom" au modèle.
$validators attend en retour un booléen .
$asyncValidators
Fonctionne de la même maniére que $validators mais pour les
traitements asynchrones.
$asyncValidators attend en retour une promise (s'utilise très
bien avec $http ).
Ces deux techniques sont à utiliser dans une directive.
$pending
L'attribut $pending permet d'afficher un message ou un
spinner en attendant la réponse du validateur asynchrone.
http://codepen.io/Ambient-IT/pen/KpvdJw?editors=101
Angular core
"data-binding under the hood"
Les watchers
A chaque fois que l'on utilise ng-model, ng-repeat et
d'autres, angular met en place des "watchers"
Un "watcher" est une simple fonction qui sera appelée
par le framework pour mettre à jour le DOM
Nous pouvons déclarer nos propres watchers...
// Si l'on souhaite observer un attribut de $scope
$scope.$watch('title', function(newVal, oldVal) {
//faire quelque chose
});
// A utiliser si on souhaite observer autre chose que le scope
$scope.$watch(function() {
return myService.data.title;
}, function(newVal, oldVal) {
//faire quelque chose
});
// Un troisième argument de type booléen permet d'observer un objet en profondeur
$scope.$watch(function(){
return myService.data;
}, function(newVal, oldVal) {
// ici c'est tout l'objet myService.data qui est observé
},true);
Mais egalement surveiller une collection (un array)
function MyController($scope){
var self = this;
self.collection = [{
id: 1,
content: 'foo'
}];
$scope.$watchCollection(function() {
return self.collection;
}, function(newVal, oldVal) {
//faire quelque chose
});
}
angular.module('app',[])
.controller('MyController',MyController);
Depuis la version 1.3 watchGroup
La méthode $watchGroup de $scope permet de mettre en
place plusieurs watchers d'un coup en specifiant un array
d'expressions
//ici $scope.foo et $scope.bar seront observés
$scope.$watchGroup(['foo', 'bar'], function(newVal, oldVal, scope) {
//faire quelque chose
});
$scope.$watchGroup(function() {
return [
myService.data,
'foo'
]
}, function(newVal, oldVal, scope) {
//faire quelque chose
});
$digest et $apply
"Dirty checking"
$scope.$digest() va exécuter tous les watchers liés au scope.
$scope.$apply() va appeler $rootScope.$digest(), ce qui va
exécuter tous les watchers.
$digest permet à angular de maintenir le modèle et la vue
synchronisés.
Le framework lance $digest à chaque fois :
qu'un bouton avec la directive ng-click est cliqué
que la valeur d'un input bindé avec ng-model change
qu'un callback asynchrone est exécuté...
http://ryanclark.me/how-angularjs-implements-dirty-checking/
Modules Complémentaires
Sécurité ngSanitize
Permet de sécuriser les saisies utilisateurs, afin d'éviter toute
injection de code malicieux.
bower install ng-sanitize --save
angular.module('app',[
'ngSanitize'
])
<script src=""></script>
ngSanitize nous permet d'utiliser la directive ng-bind-html.
Le html sera parsé et tout élément dangereux supprimé
Pour outrepasser ngSanitize et injecter du html
complexe, on peut utiliser le service $sce.
De plus le module fournit un filtre linky
Ce filtre permet de transformer toutes les URL d'une String
en lien HTML (balise <a>)
Ce filtre s'utilise avec ng-bind ou ng-bind-html.
Exercice
Intégrer ngSanitize dans notre application, et utiliser le filtre
linky sur la page de details d'un post.
animation ngAnimate
Le module ngAnimate permet d'utiliser directement des
transitions et keyframes CSS3.
bower install ng-animate --save
angular.module('app',[
'ngAnimate'
])
<script src=""></script>
ngRepeat enter and leave
ngView enter and leave
ngInclude enter and leave
ngSwitch enter and leave
ngIf enter and leave
ngShow & ngHide add and remove
form & ngModel add and remove (dirty,
pristine, valid, invalid & all other
validations)
ngMessages add and remove (ng-active &
ng-inactive)
ngMessage
Le framework va tout simplement ajouter des classes CSS à
nos éléments html quand ils entrent dans le DOM, ou juste
avant d'en sortir.
.ng-enter
.ng-enter-active
.ng-leave
.ng-leave-active
Exercice
Modifier le fichier style.css afin d'animer la liste de posts dans
le template list.tpl.html
l'animation fonctionnera conjointement avec le filtre
mobile ngTouch
Le module ngTouch fournit plusieurs directives adaptées aux
écrans tactiles
bower install ng-touch --save
angular.module('app',[
'ngTouch'
])
<script src=""></script>
Homogénéisation de ngClick, le comportement sera le même
sur mobile et desktop (délai de 300ms sur mobile)
Deux directives :
ngSwipeLeft ;
ngSwipeRight ;
Ces deux directives fonctionnent comme ngClick.
bon guide sur angular et le mobile IonicFramework
angular-gesture
Tester une application
AngularJS
Tests unitaires
Karma
Outil simplifiant l'exécution des tests
Permet de tester plusieurs navigateurs simultanément
Supporte plusieurs frameworks de test
Jasmine
Mocha
QUnit
...
Tests Fonctionnels (E2E)
Protractor
Framework de tests fonctionnels
Construit sur WebDriverJS (Selenium)
Permet de simuler l'intéraction d'un utilisateur avec le navigateur
"Black box testing"
Framework de test
Jasmine
Framework de test JavaScript
Fournit un DSL expressif
Syntaxe "Behavior Driven"
Framework par défaut pour Karma et Protractor
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});
ATDD
Tests unitaires - Squelette
describe('myComponent', function() {
beforeEach(module('myModule'));
beforeEach(inject(...));
it('should do something', function() {
...
});
})
Les fonctions module() et inject() sont
fournies par la librairie angular-mocks
module() permet de charger un module
dans le contexte du test (ne pas confondre
avec angular.module())
inject() permet d'obtenir les composants
requis pour les tests
Tester un Service
describe('myService', function() {
var myService;
beforeEach(module('myModule'));
beforeEach(inject(function(_myService_) {
myService = _myService_;
}));
it('should return true', function() {
var result = myService.getTrue();
expect(result).toBe(true);
});
})
inject() ignore les underscores du paramètre _myService_, ce qui
permet de créer une variable myService sans qu'elle soit masquée
Tester un Controller (sans $scope)
angular.module('app', [])
.controller('GreetingsCtrl', function() {
this.message = "Hello"
})
describe('GreetingsCtrl', function() {
var greetingsCtrl;
beforeEach(module('app'));
beforeEach(inject(function($controller) {
greetingsCtrl = $controller('GreetingsCtrl');
}));
it('should say Hello', function() {
expect(greetingsCtrl.message).toBe("Hello");
});
})
Tester un Controller (avec $scope)
angular.module('app', [])
.controller('GreetingsCtrl', function($scope) {
$scope.message = "Hello"
})
describe('GreetingsCtrl', function() {
var $scope, greetingsCtrl;
beforeEach(module('app'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
greetingsCtrl = $controller('GreetingsCtrl', {$scope: $scope});
}));
it('should say Hello', function() {
expect($scope.message).toBe("Hello");
});
})
Tester un Filtre
describe('limitTo', function() {
var limitTo;
beforeEach(module('app'));
beforeEach(inject(function($filter) {
limitTo = $filter('limitTo');
}));
it('should limit array to one element', function() {
var array = ['a', 'b', 'c'];
var result = limitTo(array, 1);
expect(result.length).toBe(1);
});
})
Tester une Directive
angular.module('app', [])
.directive('panel', function() {
return function(scope, element) {
element.addClass('panel');
}
})
describe('panel directive', function() {
var element;
beforeEach(module('app'));
beforeEach(inject(function($compile, $rootScope) {
element = angular.element('<div panel>Some text</div>');
$compile(element)($rootScope)
}));
it('should add class panel to element', function() {
expect(element.hasClass('panel')).toBe(true);
});
})
Tester un service utilisant $http
angular.module('app',[]) describe('Post model tests', function () {
.factory('Post',function($http){
var myService,
var url = ''; $httpBackend,
return { url = 'http://my-web-service.com/posts';
create : function(post){ beforeEach(module('app'));
return $http.post(url,post);
beforeEach(inject(function(_myService_, _$httpBackend_){
} myService = _myService_;
} $httpBackend = _$httpBackend_;
})
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should create an post', function () {
var postData = {
content: 'a content'
};
$httpBackend.whenPOST(url).respond(postData);
var createdPost;
myService
.create(postData)
.success(function(data){
createdAssessment = data;
});
$httpBackend.expectPOST(url, postData);
$httpBackend.flush();
expect(createdPost).toBeDefined();
});
});
Tests fonctionnels (E2E)
sudo npm install -g protractor
Installation de selenium sudo webdriver-manager update
Démarage de selenium sudo webdriver-manager start
Tests fonctionnels (E2E)
describe('Home screen', function () {
beforeEach(function() {
browser.get('/#');
});
it('should have title Home', function () {
expect(browser.getTitle()).toBe('Home');
});
});
La fonction browser.get() permet d'envoyer le navigateur à
l'URL de l'écran que l'on veut tester.
Simuler le comportement utilisateur
<button id="save-button" ng-click="saveMessage='Saved'">Save</button>
<div>{{ saveMessage }}</div>
describe('Save screen', function () {
beforeEach(function() {
browser.get('/#');
});
it('should display message when save button clicked', function () {
var button = element(by.id('save-button'));
var messageElement = element(by.binding('saveMessage'));
button.click();
expect(messageElement.getText()).toBe('Saved');
});
})
Liste des sélecteurs : https://github.com/angular/protractor/blob/master/docs/api.md#element
Problème
Les tests contiennent beaucoup de code de manipulation du
DOM, ce qui les rendent :
Peu expressifs
Fragiles
Le code des tests est encombré de détails concernant le
DOM, alors qu'il devrait s'intéresser uniquement aux règles
métier .
De subtiles modifications du DOM peuvent rendre de
nombreux tests inopérants, nécessitant un lourd travail de
maintenance des tests
Page Object
Il est possible de créer des objets JavaScript qui fournissent une API
permettant de manipuler chaque vue à tester.
Ces objets encapsulent le code de manipulation du DOM, cachant ainsi les
détails d'implémentation
Si le DOM change, seul le Page Object doit être mis à jour
L'API fournie est à un niveau d'abstraction élevé, adapté à l'écriture et à la
lecture des tests fonctionnels
Page Object - Exemple
function SavePage() {
this.saveButton = element(by.id('save-button'));
this.saveMessageElement = element(by.binding('saveMessage'));
this.get = function () {
browser.get('/#');
};
this.getSaveMessage = function () {
return this.saveMessageElement.getText();
}
}
module.exports = SavePage;
Tester avec un Page Object
var SavePage = require('./SavePage');
describe('Save page', function () {
var page = new SavePage();
beforeEach(function() {
page.get();
});
it('should display message when save button clicked', function () {
page.saveButton.click();
expect(page.getSaveMessage()).toBe('Saved');
});
})
End !
Et après ?
les tuto video d'egghead
ng-newsletter
meetup angular
Angular material
Ionic
Retrouvez les exemples de ce support sur codepen
https://github.com/AmbientIT/AngularJs-exercice
https://github.com/Charl---/formationAngularJs/tree/correction
ng-conf 2015
Support, exemples et exercices conçus par
Mikael Couzic et Charles Jacquin
Annexes
$resource
Niveau d'abstraction plus élevé que $http.
Nécessite d'importer le package ngResource.
Avantages :
Très simple à mettre en place, quelques
lignes suffisent
Inconvénients :
Documentation illisible
Pas de promise jusqu'à la version 1.2