AngularJS-Shield-large
AngularJS

AngularJS expliqué en patrons de conception, épisode 3 : Les Directives

Dans cet épisode, nous allons voir comment les patrons de conception traditionnels sont utilisés par les directives d’AngularJS.

Cet article fait partie d’une série d’articles consacrés à l’utilisation des patrons de conceptions par et dans les composants d’AngularJS.

Composite

Ce patron permet de composer une hiérarchie d’objets, et de manipuler de la même manière un élément unique, une branche, ou l’ensemble de l’arbre. Il permet en particulier de créer des objets complexes en reliant différents objets selon une structure en arbre.

Composite

D’après le Gang of Four (GoF), le patron MVC n’est rien de plus que la combinaison de :

  • Strategy
  • Composite
  • Observer

Ils explique que la vue est la composition de plusieurs composants. Dans AngularJS, nous sommes dans ce cas de figure. Les vues sont formées grâce à la composition des directives et les éléments DOM.

Voyons cet exemple :

<!doctype html>
<html>
  <head>
  </head>
  <body>
    <zippy title="Zippy">
      Zippy!
    </zippy>
  </body>
</html>
myModule.directive('zippy', function () {
  return {
    restrict: 'E',
    template: '<div><div class="header"></div><div class="content" ng-transclude></div></div>',
    link: function (scope, el) {
      el.find('.header').click(function () {
        el.find('.content').toggle();
      });
    }
  }
});

Dans cet exemple, nous avons définit une simple directive, qui se trouve être un composant de type UI. Ce composant appelé “zippy” possède un entête div.header et un contenu div.content.

A partir du premier exemple, nous pouvons remarquer que tout l’arbre DOM est la composition de plusieurs éléments. L’élément racine est html, suivi juste après par head, ensuite body… etc.

Dans le second exemple JavaScript, la propriété template de la directive contient une directive ng-transclude (sous forme d’attribut). Ceci signifie qu’à l’intérieur de la directive zippy nous avons une autre directive nommée ng-transclude. Autrement-dit, une composition de directive.

Interpreter

Le patron comporte deux composants centraux : le contexte et l’expression ainsi que des objets qui sont des représentations d’éléments de grammaire d’un langage de programmation. Le patron est utilisé pour transformer une expression écrite dans un certain langage programmation – un texte source – en quelque chose de manipulable par programmation: Le code source est écrit conformément à une ou plusieurs règles de grammaire, et un objet est créé pour chaque utilisation d’une règles de grammaire. L’objet interpreter est responsable de transformer le texte source en objets.

Interpreter

Grâce au service $parse, AngularJS propose sa propre implémentation d’une DSL (Domain Specific Language). Cette DSL est une version très simplifiée du langage JavaScript.

Voici les spécificités des expressions AngularJS :

  • elles peuvent contenir des filtres via la syntaxe pipe (à la manière d’UNIX)
  • ne déclenche pas d’exceptions
  • ne contient aucune structure de contrôle (même s’il est possible d’utiliser l’opérateur ternaire)
  • sont évaluée dans le contexte du $scope de la vue

Dans l’implémentation du service $parse, sont définis deux composants :

//Responsable de convertir des caractères en token
var Lexer;
//Responsable d'interpréter les token et d'évaluder les expressions
var Parser;

Lorsqu’une expression a été transformée en token, elle est mise en cache pour des raisons de performance.

Les symboles terminaux dans la DSL AngularJS sont définis comme ceci :

var OPERATORS = {
  /* jshint bitwise : false */
  'null':function(){return null;},
  'true':function(){return true;},
  'false':function(){return false;},
  undefined:noop,
  '+':function(self, locals, a,b){
        //...
      },
  '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
  '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
  '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
  '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
  '=':noop,
  '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
  '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
  '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
  '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
  '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
  '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
  '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
  '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
  '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
  '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
  '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
  '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
  '!':function(self, locals, a){return !a(self, locals);}
};

Nous pouvons considérer chaque fonction associée à un symbole comme étant une implémentation de l’interface AbstractExpression.

Chaque Client interprète une expression AngularJS donnée dans un contexte spécifique.

Voici quelques exemples d’expressions AngularJS :

// le filtre toUpperCase est appliqué au résultat de l'expression :
// (foo) ? bar : baz
(foo) ? bar : baz | toUpperCase

Template View

Affiche un rendu dans l’HTML, basé sur des marqueurs dans le code HTML.

Template View

La gestion du rendu dynamic d’une page n’est pas chose facile. Il est souvent question de beaucoup de manipulation de chaîne de caractères. Pour simplifier cette procédure, il est souvent plus simple d’incorporer des expressions dans notre HTML, ces expressions sont évaluées dans un contexte donné et tout le template est ensuite compilé vers le format final. Dans notre cas, ce format est HTML voire le DOM. C’est ce que font les moteurs de templating – ils prennent une DSL, l’évaluent dans un contexte donné puis la transforment en un format souhaité.

Les templates sont historiquement associés aux technologies back-end. Par exemple, vous pouvez embarquer du code PHP dans une page HTML pour créer une page dynamique, en utilisant un moteur de templating, tel que Smarty.

En ce qui concerne JavaScript, il existe pléthore de moteurs de templating, les plus connus sont mustache.js et handlebars.js. Ces moteurs manipulent souvent les templates sous format texte. Ces templates peuvent être mis directement dans la page statique, dans une balise script avec un type spécifique, récupéré depuis le serveur via XHR  ou encore mis directement dans des balises scripts JavaScript.

Par exemple :

<script type="template/mustache">
  <h2>Names</h2>
  {{#names}}
    <strong>{{name}}</strong>
  {{/names}}
</script>

Le moteur de templating transforme ce texte (le contenu de la balise script) en éléments DOM en les compilant dans un contexte donné. Par exemple, si ce contenu est évalué dans le contexte suivant : { names: ['foo', 'bar', 'baz'] }nous obtenant ceci :

<h2>Names</h2>
  <strong>foo</strong>
  <strong>bar</strong>
  <strong>baz</strong>

En AngularJS, les templates n’ont pas besoin d’être compilés : ils ne sont pas dans un format intermédiaire comme le template précédent. Ce que fait le compilateur AngularJS, il traverse l’arbre DOM à la recherche des directives connues (éléments, attributs, classes CSS ou encore des commentaires, oui c’est rare mais cela existe). Lorsqu’il rencontre l’une de ces directives, il invoque le code associé ce qui déclenche l’évaluation des différentes expressions dans le contexte courant.

Par exemple :

<ul ng-repeat="name in myCtrl.names">
  <li>{{name}}</li>
</ul>

L’évaluation de la directive ng-reapeat  dans le contexte suivant :

myCtrl.names = ['foo', 'bar', 'baz'];

produit le même résultat que précédemment :

<h2>Names</h2>
  <strong>foo</strong>
  <strong>bar</strong>
  <strong>baz</strong>

C’est tout pour cet épisode.

Dans les prochains épisodes…

Dans le prochain, nous allons voir comment les patrons de conception classiques sont utilisés par le scope d’AngularJS. Ce sera intéressant pour les développeurs qui utilisent toujours le service $scope.

Standard