Le W3C est en train de plancher sur un enrichissement de la norme HTML5 avec la spécification sur les Webs Components. Cette spécification permet d’aller plus loin dans la création de widgets personnalisés en introduisant la notion de composants HTML réutilisables. Cette norme n’est pas encore finalisée et le framework Angular JS a mis en place sa propre solution pour répondre à cette problématique, les directives. Angular JS est améné a se caler sur cette norme dans sa future version 2.0.
Une directive est donc un élément personnalisé dans un document HTML. Ce peut être un attribut, une classe CSS ou une balise HTML. Le compiler HTML d’Angular convertit ces directives pour afficher le résultat final. Le framework propose ses propres directives comme ngModel, ngView mais il offre aussi la possibilité de créer ses propres composants.
Générer une directive simple à l’aide de Yeoman
Il est temps d’introduire un exemple et si vous utilisez Yeoman la mise en place est très simple. Lancez la commande (veillez à ne pas mettre de majuscule au début de votre directive pour ne pas avoir de problème après)
yo angular:directive filAriane
Yeoman va vous générer deux fichiers dans l’arborescence projet : un contenant la directive à proprement parlé et un fichier pour tester cette directive
- app\scripts\directives\filAriane.js
- test\spec\directives\filAriane.js
angular.module('supervisorApp') .directive('filAriane', function () { return { template: '<div></div>', restrict: 'E', link: function postLink(scope, element, attrs) { element.text('this is the FilAriane directive'); } }; });
Maintenant vous pouvez ajouter la balise <fil-ariane></fil-ariane> dans une de vos pages HTML. Si on revient sur le code de la directive vous pouvez observer que nous déclarons
un template qui contient le code HTML de la directive
- restrict: ‘E’ permet de préciser que la directive est utilisée sous forme de balise HTML. Vous pouvez utiliser ‘A’ si vous voulez ajouter un paramètre déclaré de la manière suivante <div fil-ariane></div>
- link pour ajouter des fonctions permettant de manipuler le DOM
Si on regarde la classe de test jasmine nous avons
describe('Directive: FilAriane', function () { // load the directive's module beforeEach(module('supervisorApp')); var element,scope; beforeEach(inject(function ($rootScope) { scope = $rootScope.$new(); })); it('should make hidden element visible', inject(function ($compile) { element = angular.element('<fil-ariane></fil-ariane>'); element = $compile(element)(scope); expect(element.text()).toBe('this is the FilAriane directive'); })); });
Enrichir une directive
Cet exemple est simple donc regardons maintenant comment ajouter un vrai fil d’ariane qui utilisera une variable de scope déclarée dans un controller
$scope.ariane = { name : "PageEncours", histo : [{ name :"Pageavant", link :"/mapage" }] };
AngularJS est un framework qui permet de créer des vues en HTML. Il en est de même sur les éléments personnalisés. Dans le premier exemple nous avons défini le code dans le Javascript et nous allons maintenant le faire dans un fichier HTML nommé par exemple /app/views/components/fil-ariane.html.
<div> <ol class="breadcrumb"> <li ng-repeat="histo in ariane.histo"> <a ng-href="#{{histo.link}}">{{histo.name}}</a> </li> <li class="active">{{ariane.name}}</li> </ol> </div>
Le code de notre directive est modifié de la manière suivante
angular.module('supervisorApp') .directive('filAriane', function () { return { templateUrl: 'views/components/fil-ariane.html', restrict: 'E', }; });
L’attribut template est remplacé par templateUrl pour associé notre fragment de code HTML à la directive.
Tester une directive utilisant un template HTML
On arrive à la partie intéressante de l’article avec la mise à jour de notre test. La difficulté est liée au fait que le code de la directive est défini dans un fichier html non connu dans le contexte de test. Dans plusieurs articles j’ai vu des personnes qui redéfinissaient le contenu de leur template HTML dans le code Javascript du test. Si le template est compliqué ou que vous faites des modifications à l’intérieur la maintenance du test devient vite problématique.
La solution est de se baser sur la librairie html2js qui permet de convertir une page HTML en JS. Pour s’interfacer les tests d’applications Angular avec karma, il existe une librairie karma-ng-html2js-preprocessor qui lance html2js et charge les pages dans un objet $templateCache utilisable dans nos tests
Pour installer le module utilisez la commande
npm install karma-ng-html2js-preprocessor --save-dev
Il faut ensuite modifier le fichier karma.conf.js de votre projet pour utiliser ce preprocessor avant le lancement de vos tests
module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine'], // generate js files from html templates to expose them during testing. preprocessors: { 'app/**/*.html': ['ng-html2js'] }, // list of files / patterns to load in the browser files: [ 'app/bower_components/**/*.js', 'app/**/*.js', 'test/**/*.js', 'app/**/*.html' ], ngHtml2JsPreprocessor: { moduleName: 'templates' }, exclude: [], port: 8080, logLevel: config.LOG_INFO, autoWatch: false, browsers: ['Chrome'], singleRun: false }); };
Toutes les étapes préliminaires sont finies et regardons comment écrire une classe de tests de la directive
describe('Directive: FilAriane', function () { // on charge le module templates défini dans la conf karma beforeEach(module('supervisorApp','templates')); var element, scope, template; beforeEach(inject(function ($rootScope, $compile, $templateCache) { //Je declare un element correspondant a la declaration de ma directive element = angular.element('<fil-ariane></fil-ariane>'); //Recuperation de la page HTML convertit en JS template = $templateCache.get('app/views/components/fil-ariane.html'); //On place dans le cache des templates la page definit dans templateUrl de la directive $templateCache.put('views/components/fil-ariane.html', template); //On place dans le scope ce que la directive attend scope = $rootScope; scope.ariane = { name: "pageEncours", histo: [{ name :"pageAvantAvant", link :"/mapageavant" },{ name :"pageAvant", link :"/mapage" }] }; //L'element est compile en lui injectant le scope defini $compile(element)(scope); scope.$digest(); })); it('doit avoir 3 occurences', inject(function ($compile) { expect(element.find("li").length).toBe(3); })); it('doit avoir 2 liens vers les pages precedentes', inject(function () { expect(element.find("a").length).toBe(2); expect(element.find("a").text()).toBe("pageAvantAvantpageAvant"); })); });
Dans la première partie nous injectons le module définit dans la la conf karma
beforeEach(module('supervisorApp','templates'));
Le principe du test consiste ensuite à
- déclarer la directive à tester
- charger le template HTML lié
- charger les données dans le scope utilisées par la directive
- compiler la directive en lui injectant le scope
- et tester...
Aucun commentaire:
Enregistrer un commentaire
Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.