Ceux qui ont utilisé un moteur de template logicless comme Mustache ou Handlebars savent immédiatement de quoi je parle. Un template doit contenir le minimum possible de logique métier. Et avec Mustachejs ou Handlebarsjs, le concept est poussé loin puisque c’est tout simplement impossible.
Logic-less templates – Semantic templates
J’ai découvert Handlebars sur mon premier projet EmberJs et son utilisation m’a paru bien déroutante au début. Voici un exemple de template Handlebars :
{{#if person.isAdmin}} Welcome in admin area, <strong>{{person.firstName}} {{person.lastName}}</strong>! {{/if}}
Jusque là, rien d’anormal ! Comment faire maintenant si je veux modifier un peu ma condition pour afficher ce bloc si la personne est Admin ou si elle est Super Admin ? L’extrait de code suivant semble tout indiqué :
{{#if person.isAdmin or person.isSuperAdmin}} Welcome in admin area, <strong>{{person.firstName}} {{person.lastName}}</strong>! {{/if}}
Pourtant c’est impossible ! Nous sommes dans un moteur de template logicless et nous n’avons pas la possibilité d’utiliser ce type de condition complexe.
Nous sommes dès lors obligés d’extraire la logique de notre template et d’injecter une condition simple, calculée en dehors. Exemple :
{{#if isAdminAreaAuthorized}} Welcome in admin area, <strong>{{person.firstName}} {{person.lastName}}</strong>! {{/if}}
Cela semble très contraignant au début mais on s’y fait finalement vite.
Ce type de template est en revanche parfaitement possible au sein d’une application AngularJs :
<p ng-show="person.isAdmin || person.isSuperAdmin"> Welcome in admin area, <strong>{{person.firstName}} {{person.lastName}}</strong>! </p>
Sur l’extrait de code précédent, la logique implémentée au sein du template reste assez concise. Mais je suis trop souvent tombé sur ce genre de templates sur des applications AngularJS :
<p ng-show="!isWorkInProgress && currentItem.price > 1000 && (user.id == currentItem.authorId || user.isAdmin || user.isSuperAdmin)"> {{ currentItem.title }} </p> <p>Un paragraphe qui s'affiche toujours</p> <p ng-show="!isWorkInProgress && currentItem.price > 1000 && (user.id == currentItem.authorId || user.isAdmin || user.isSuperAdmin)"> {{ currentItem.description }} </p>
Problèmes
Nous avons ici plusieurs problèmes :
- Les conditions sont assez obscures et difficiles à comprendre
- Les conditions sont répétées en plusieurs endroits au sein du même template
- Mélange de logique métier et de logique d’affichage
- La logique business est probablement dispatchée en différents endroits du code (des controllers, des services, des templates, …)
Difficile pour un intégrateur de travailler sur un template de ce type. Et quid du changement de condition pour l’affichage de nos blocs ? Nous sommes bons pour un chercher / remplacer douloureux.
Les moteurs de templates logic-less tels que que Mustache ou Handlebars sont loin de faire l’unanimité parmi les développeurs. En cause, leur aspect trop contraignant et l’obligation d’extraire la logique purement de présentation en même temps que la logique business. C’est vrai que finalement, la condition isWorkInProgress
est liée uniquement à l’affichage et peut trouver légitimement sa place au sein du template.
Néanmoins, ces moteurs de templates logicless ont le mérite de forcer le développeur à extraire la logique business du template pour la centraliser au sein du controller ou de services dédiés.
Pour avoir le point de vue de personnes se positionnant contre les moteurs de templates logic-less, je vous conseille la lecture de The Case Against Logic-less Templates et de cet autre article dans la même veine : Cult of logicless templates.
AngularJS : extraction de la logique vers les controllers et services
En ce qui concerne vos applications AngularJS, de même qu’il est important de simplifier au maximum les controllers, il est important de limiter le plus possible la logique métier implémentée au sein des templates.
Dans les faits, le template précédent pourrait être repensé de la sorte :
<p ng-show="isItemViewableByUser(currentItem, user)"> {{ currentItem.title }} </p> <p>Un paragraphe qui s'affiche toujours</p> <p ng-show="isItemViewableByUser(currentItem, user)"> {{ currentItem.description }} </p>
Notre template est plus clair. Le nommage sémantique de la fonction qui conditionne l’affichage (isItemViewableByUser
) rend le code compréhensible immédiatement. De plus, si la logique métier qui conditionne l’affichage change, le template n’a plus à changer.
Nous injectons cette logique dans le scope
via le controller associé. Et pour ne pas faire l’erreur de remonter telle quelle une condition métier au sein du controller, n’oubliez pas d’extraire à son tour la logique métier de ce dernier vers un service dédié, rendant le tout plus facile à tester unitairement, plus facile à comprendre et donc plus facile à maintenir :
function MyCtrl($scope, UserService) { $scope.isWorkInProgress = false; // [...] $scope.isItemViewableByUser = function(item, user) { // Au choix, logique remontée au sein du Model // cf http://blog.overnetcity.com/2014/03/15/angularjs-models-donnees-model-data return !isWorkInProgress && user.canSeeItem(item); // ou au sein d'un service dédié return !isWorkInProgress && UserService.doesUserCanSeeItem(user, item); }; }
Pour aller plus loin
Quelques ressources pour aller plus loin :
Merci pour votre article, l’organisation du métier c’est important dans une application de grande échelle, j’utilise CanJS et souvent je met la logique métier dans un can.Map et il a can.mustache je dévelope un can.Mustache.helper pour passer la logique au template ça devrais marcher dans Handlebars de EmberJS http://www.bitovi.com/blog/2014/02/data-driven-javascript-controls.html
Développeur full-stack à mon compte depuis plus de 4ans, je suis à 100% d’accord avec ce qui est dit dans cet article! Mon plus gros client est une agence de communication et dans ce contexte là, ce qui change le plus souvent c’est la logique métier et non pas le rendu graphique. Chaque demande d’évolution m’expose alors à un bug de rendu sur une des 150 combinaisons OS/Browser alors qu’elle ne concerne absolument pas l’aspect graphique mais uniquement la logique métier. Sans oublier les innombrables vidages de caches de rigueur pendant les phases de développement…
Extraire la logique métier de mes templates m’a permis de gagner en agilité et en stabilité.