Le framework AngularJS est un socle technique javascript open-source créé par Google en 2009. Plus précisément, par Miško Every, Vojta Jina et Igor Minar. Présenté en Europe en 2012 à Devoxx Belgique, le framework a immédiatement rencontré un excellent accueil de la part des développeurs. Aujourd’hui, c’est l’un des frameworks JS les plus connus.
Quelques principes et choix techniques permettent à AngularJS de se distinguer de ses concurrents. Ainsi on cite souvent :
- Le choix d’une approche déclarative, où la partie vue devient du HTML dynamique
- Un binding bi-directionnel entre la vue et le modèle
- Une approche MVC classique avec une séparation forte entre les différents composants
- Un bon support de la ré-écriture des liens et de ce que l’on appelle deep-linking
- La possibilité de créer des composants réutilisables
- Un bon support de tout ce qui est I18N
- Enfin la possibilité de tester facilement (mocks)
AngularJS est populaire. Pour s’en convaincre, il suffit de regarder le nombre d’offres d’emplois, de voir qu’il y a la conférence ng-Europe de 2 jours prévues à Paris le 22/23 octobre prochain, ou de regarder sur Twitter les sujets du moment. A priori pas aussi populaire que Backbone.js (source) mais bien plus qu’Ember.js ou Meteor.
Du côté Github, voici ce que donne les chiffres au 8 octobre 2014 :
Watches | Stars | Fork | Commiters | |
---|---|---|---|---|
Ember.js | 1490 | 11409 | 2476 | 407 |
AngularJS | 2858 | 29684 | 11166 | 1000 |
Meteor | 1164 | 18987 | 2087 | 140 |
Backbone.js | 1490 | 19293 | 4362 | 234 |
Selon ces chiffres sur Github, AngularJS a une belle communauté de contributeurs.
Pour ma part, j’ai utilisé AngularJS pour Zaptravel et surtout pour le CFP de Devoxx France et de Devoxx Belgique. J’ai bien aimé la prise en main, la documentation et l’approche globale. Moins de pièges qu’avec Backbone, et surtout quelques choix forts que j’ai apprécié. AngularJS est utilisé sur le CFP pour construire les agendas des conférences. Ceci nous permet par glisser-déposer de construire les agendas.
Je trouve qu’AngularJS est parfait pour le développeur Java standard. Ce n’est pas un scoop, c’est simplement car les développeurs principaux étaient d’abord des dèvs Java, avant de devenir des développeurs Javascript. Cela transpire dans l’approche et les idées du framework. C’est du MVC, avec une partie controller, des services et des directives. A titre personnel, j’aime le binding bi-directionnel. Il est possible d’écrire rapidement une application avancée. En ajoutant underscorejs ou lo-dash, vous avez tout ce qu’il faut pour écrire du code fonctionnel.
Jusqu’ici tout va bien… voyons le côté sombre d’AngularJS maintenant.
Le risque d’avoir du AngularJS partout
Je vous pose la question : est-ce qu’AngularJS est le choix le plus pertinent pour votre projet, par rapport à jQuery ?
Un développeur JS front-end expérimenté maîtrise jQuery. Il peut donc couvrir les besoins de base, sans qu’il soit nécessaire d’utiliser AngularJS. Imaginons un instant que vous n’êtes pas spécialement développeur front-end. Si vous ne connaissez qu’AngularJS, vous serez tenté de vouloir tout faire avec. Or l’écosystème JS est énorme. Il grandit chaque jour. Il sort un nombre d’outils impressionnants. Et donc de frameworks. On rejoue ici la partition de la complexité accidentelle. AngularJS n’est pas un framework « léger ». Ne vous fiez pas aux exemples de la page d’accueil. Bien que pertinent, un projet AngularJS vous demandera un ticket d’entrée un peu plus élevé, que quelques lignes de javascript avec jQuery.
AngularJS : c’est vraiment bien pour les singles pages applications
AngularJS est un framework côté client, qui tourne dans le navigateur. Il faut donc le coupler avec un serveur Web, capable d’échanger simplement à travers le protocole HTTP. Cela favorise le développement de ce que l’on appelle les « singles pages application » : votre application AngularJS tourne dans votre navigateur. Cela entraîne un couplage entre la partie vue et le modèle. Plus important : cela entraîne que du code et de la logique seront exécutés du côté du navigateur. C’est souhaitable pour les performances, mais cela peut entraîner une certaine forme de complexité. Est-ce que ce bug est du côté serveur, ou dans mon code JS ?
AngularJS ne retire pas le besoin d’avoir un framework côté serveur, pour construire son application. L’usage le plus courant c’est de prendre Node.JS côté serveur. Attention cependant : si le langage est le même, la façon de développer avec Node n’est pas du tout la même façon de coder une application avec AngularJS. J’ai peur de voir du code JS écrit par un développeur Front. J’ai peur de voir du code AngularJS écrit par un développeur Node…
Il existe donc une alternative à l’approche single-page-app, que l’on retrouve par exemple dans le framework Meteor. L’idée est simple : la logique métier peut s’exécuter du côté client, comme du côté serveur. Et le framework abstrait le besoin de décrire un protocole d’échange entre le client et le serveur. La promesse est la suivante : le framework prend à sa charge les échanges avec le serveur. Plus besoin d’interagir avec une API REST.
Cette approche porte un nom : isomorphic javascript. Du code JS peut aussi bien tourner du côté serveur, que dans la VM de votre navigateur.
Malheureusement c’est une idée éculée et qui n’est pas adaptée à tous les types de projets. Mais avant d’aller à la conclusion, voyons d’abord de quoi il en retourne.
Isomorphic Javascript est tombé dans un article en 2011 sur le blog de Nodejistu, puis repris il y a un an, sur le blog d’Airbnb. Si vous n’avez jamais entendu parler de MVC, Model2, MVP, je vous recommande la lecture de l’article de Charlie Robbins sur ce sujet.
Javascript est un langage isomorphic, qui peut tourner aussi bien du côté serveur, que du côté du client web. Pourquoi ne pas remplacer l’architecture MVC classique côté client, portée par AngularJS, par une approche client serveur ? On revient à une approche client-serveur, et donc exit l’application single-page dont le code ne tourne que du côté du navigateur.
Le postulat de départ dans l’article d’Airbnb, est que les applications single-page ne sont pas web friendly. En effet, une application single-page est un bloc de code côté client, qui inter-agit avec le serveur, et qui demande finalement un client puissant. Malheureusement, dès lors que vous cassez les standards du web, vous devez en supporter les conséquences. AngularJS s’en sort bien pour ce qui est du support des URLs bookmarkables ou du bouton « Retour » du navigateur. Mais s’agissant d’une application, oui vous aurez des soucis de SEO. Oui le support de plusieurs fenêtres du navigateur est compliqué avec une application single-page
Et donc, les développeurs d’Airbnb ont pensé qu’il serait peut-être mieux de ne pas faire QUE des singles page-app pour le site. D’où l’article de blog et un autre buzz word : isomorphic javascript.
Les arguments d’Airbnb à l’encontre d’une approche uniquement single-page sont :
- difficile de faire du SEO
- les performances sont moyennes
- le code est dur à maintenir
Tout d’abord le SEO. En effet, c’est critique pour Airbnb. Sur ce point, il est plus simple de créer des pages « spéciales robots » plutôt que de venir toucher à l’application Javascript. La réponse ne se trouve pas du côté client pour moi, mais de la ressource qui est rendue par le serveur. N’oublions pas que nous faisons du Web. S’il y a une balise User-Agent dans le protocole HTTP, c’est qu’elle a été pensée pour que vous puissiez présenter une ressource différemment. S’il y a un header Accept, c’est aussi justement pour que votre application soit capable de retourner du contenu indexable. C’est le B-A-BA du protocole HTTP.
Les performances sont moyennes. Airbnb dans son article explique que le fait que du contenu soit rendu par du javascript peut casser l’expérience utilisateur. Le visiteur lambda verra la page se charger, puis ensuite verra le contenu arriver et se mettre en place. C’est ce qui arrive sur une application ‘à la single-page’ classique. C’est tout simplement parce que votre application manipulera le DOM dans le navigateur, et qu’en effet, cela peut entraîner un effet « chargement » désagréable. Je ne suis pas du tout convaincu qu’une approche isomorphice supprime cet effet. Sur mobile, avec une connexion médiocre, ce sera peut-être même pire qu’une approche à la single-page.
Un autre souci de performance, crucial sur mobile, c’est lorsque le framework entraîne beaucoup trop d’appels au serveur. Exemple type : vous écrivez une application AngularJS qui consomme l’API REST de Devoxx. Si votre application AngularJS ne prend pas en compte les headers HTTP que vous retourne l’API REST, alors vous serez amené à recharger systématiquement toutes les données. C’est dommage lorsque l’on sait qu’il existe des solutions. L’API de Devoxx retourne des ETags pour justement éviter des rechargements intempestifs. Allez voir le projet restangular de Martin Gontovnikas. Il était speaker à Devoxx France 2014 (où pour info, on ne parle pas QUE de Java, mais aussi beaucoup de web et de JS)
Le code est dur à maintenir. En effet, il est très facile de coller de la logique dans les templates d’une application AngularJS, et de se mélanger les pinceaux avec la partie controller. Soit. Il suffit cependant de suivre les principes et les exemples de projets, qui justement, évite d’avoir du code spaghetti.
Donc isomorphic javascript ou single-page-application ?
Meteor ou AngularJS ?
Conclusion
AngularJS est un bon framework, qui à titre personnel, me permet d’écrire efficacement des applications, avec un backend en Scala. Comme exposé plus haut, ne cédez pas à la tentation de l’utiliser systématiquement. Ce ne doit donc pas être une silver bullet. Simplement un autre framework à connaître.
Je suis plus réservé sur l’architecture proposée par les frameworks dit isomorphiques. Cela nous ramène sur des patterns que nous avons déjà vu dans la communauté Java. RMI (Remote method invocation) est une API qui permet d’exécuter du code Java, en masquant le fait que les objets Java s’exécutent au sein d’une autre JVM. Or lorsque vous faîtes du Web, ce qui reste le cas dans l’approche de Meteor, n’est-il pas préférable d’avoir le contrôle sur la partie serveur ? Quid des firewalls ? De l’optimisation des appels client-serveurs ? De l’utilisation des standards HTTP tels que les ETags/If-None-Match ?
Pour conclure sur AngularJS, oui c’est un très bon framework JS. Si vous découvrez vraiment la programmation Javascript par ce biais, il reste cependant indispensable de connaître aussi d’autres frameworks plus légers comme jQuery.
Donc certainement pas une silver-bullet.
Les silver-bullet, comme les vampires qu’elles sont censées tuer ça n’existe pas. Quand on choisis un techno c’est avec ses avantages et ses inconvénients.
Ca me fait pense à l’excellent conf de Neal Ford au premier Devoxx FR : Abstraction / Distraction. Globalement il faut comprendre les abstractions que l’on utilise et ne pas se laisser distraire par elle.
Avec Angular, on est typiquement dans un cas ou le double data binding fournit une abstraction qui est agréable à utiliser. Elle ne doit cependant pas nous distraire et on doit comprendre comment ça marche pour ne pas pénaliser les performances en réévaluant le DOM 50000 fois par seconde.
Pour les framework type Meteor, l’abstraction est celle qui nous cache la liaison client/serveur. Encore une fois, il ne faut pas se laisser distraire : en dessous ça reste HTTP. J’ai vu des app GWT qui pour mettre à jour une valeur d’un tableau passait tout le tableau modifié en POST…. Ca fonctionne sur le post du dév, mais pas en prod ou pour des raisons de sécurité on limite la taille des requêtes POST.
« plus simple de créer des pages « spéciales robots » » Google apprécie ce genre de pratique ? Les spécialistes du SEO que j’ai rencontré m’ont dit le contraire.
Voila ce que je trouve sur sur StackOverflow « And yes, if Google finds out your serving different content, you might be penalized » http://stackoverflow.com/questions/7549306/single-page-js-websites-and-seo
Sinon je suis en train de faire une expérimentation « isomorphic » avec React et Nashorn 🙂
Hello, merci Nico !
Mais tout le monde en parle c’est dingue !
« Le code est dur à maintenir. » : je suis assez d’accord, on peut vite mal écrire ou avoir des mauvaises pratiques et cela trop facilement. Je fais justement une petite série de bonnes pratiques sur mon blogue : blog.xavier-carpentier.com J’espère que cela pourra en aider certains.
Tu sera à ng-europe ou ça tombe trop proche de Scala.IO ?
@+
@xcapetir
Un petit complément au niveau des points noirs d’angularjs il y a également le gap entre le niveau nécessaire pour faire une appli et le niveau nécessaire pour faire une directive.
Si l’appli nécessite d’écrire des directives, il est obligatoire d’avoir un développeur qui maîtrise bien les arcanes du framework.
En terme de gestion de la maintenance, si un problème survient au niveau d’une directive n’importe qui ne peut pas debugguer la chose.
La présentation de Peter Hunt sur react.js (http://www.youtube.com/watch?v=x7cQ3mrcKaY) présente bien ces problèmes liés aux notions spécifiques à angularjs qu’il faut maîtriser : compile, linking, transclusion, …
@xavier : je serai à Scala.IO 🙂
Hello,
Perso j’ai testé Angular, c’est pas mal mais je trouve ca un peu lourd comme framework.
Puis j’ai testé React, et ça m’a semblé carrément mieux. Beaucoup plus léger, React ne fait pas grand chose mais il le fait tres bien.
Comme tu parles souvent de Scala et de programmation fonctionnelle je me permet une petite présentation qui montrera les avantages de React sur Angular.
———————————————
React a de base une approche beaucoup plus fonctionnelle, dans laquelle on transforme du state JSON en DOM virtuel dans une fonction pure. Donc faire évoluer son state permet de faire évoluer le DOM virtuel. A partir des changements de DOM virtuel, React est capable d’appliquer les différentiels sur le vrai DOM. Les manipulations DOM étant lentes, React applique ces changements réduisant au maximum le nombre d’opérations sur le vrai DOM. React est aussi capable d’optimiser le calcul du DOM virtuel en detectant les « morceaux » du state qui n’ont pas changé.
Le problème c’est que React a 2 manières de gérer le state: un state qui provient de l’exterieur du composant (potentiellement d’un composant parent), et un state local au composant. Du coup le state global est un peu eparpillé à droite et à gauche (vous me direz c’est la meme chose avec les scopes d’Angular, les vues de Backbone…)
———————————————————
Mais il existe des approches beaucoup plus fonctionnelles pour React. Imaginez si on stockait tout le state dans un gros objet JSON en dehors de React. Et qu’on utilisait simplement React pour faire le rendu de ce state global? Ca veut dire que React devient une sorte d’enorme template, qui transforme le gros state JSON en DOM virtuel dans une fonction pure, et ensuite applique le diff sur le vrai DOM.
Vous me direz que ca doit couter cher de recalculer a chaque fois tout le DOM virtuel dès qu’on modifie un simple petit attribut du gros state JSON (par exemple quand quelqu’un tape une lettre dans un input texte).
Mais maintenant imaginez que le state JSON est une « persistent data structure », une structure immuable de programmation fonctionnelle avec du structural sharing. Il devient alors tres facile de comparer le State1 avec le State2, et de trouver les « chemins » qui varient entre ces 2 états -> Au lieu de faire une comparaison en profondeur des attributs du state, il suffit d’utiliser une comparaison par identité (=== en javascript). A partir de la on peut facilement dire a React de ne recalculer que les branches du DOM virtuel qui ont véritablement changé, et la ça devient beaucoup plus rapide.
En pratique les composants React s’imbriquent les uns dans les autres (un peu comme les directives avec templates dans Angular). Et on passe aux composants des sortes de pointeurs sur le state global (un peu comme des lenses). Du coup le calcul du DOM virtuel pour un composant donné ne se fait que si la valeur derrière le pointeur a changé depuis le dernier rendu.
——————————————–
Bref je rentre pas plus dans les détails mais tout ca pour dire qu’injecter un etat global a une fonction pure qui recalcule tout le DOM de manière efficace est possible, avec de tres bonnes performances. C’est un concept tres simple, c’est tres facile a raisonner et débugger. En fait, a chaque fois que quelque chose change visuellement dans votre application, vous ouvrez votre console et vous pouvez logger le state qui a produit ce DOM.
Du coup, vous ne touchez plus du tout au DOM. Vos erreurs sont soit un probleme de management du state, soit un probleme de transformation du state en DOM.
Le management du state reste assez complexe sur une SPA qui n’a pas toutes les données en local et qui doit optimiser les appels, mais par contre, avec cette approche, le DOM n’est vraiment plus du tout un problème.
——————-
Pour plus de détails, regarder les présentations sur Om, une interface Clojurescript au dessus de React, qui a popularisé ces concepts.
https://www.youtube.com/watch?v=DMtwq3QtddY
A noter que depuis il existe d’autres frameworks qui ont été créés sur ces concepts, dont certains en pure JS: http://stackoverflow.com/questions/25791034/om-but-in-javascript
J’ai testé cette approche et je peux vous dire que même sur du Phonegap ça marche très bien malgré le petit CPU de nos téléphones 🙂
Merci Sébastien pour ton retour et ton article 🙂
Sur Twitter aussi j’ai eu beaucoup de retours sur React.js suite à la publication de cet article. A tester donc rapidement.
Hello,
Pas d’accord avec le ticket d’entré. Il est au contraire bas dixit justement la page d’accueil. Il est plus bas et moins verbeux qu’un pendant Jquery pur. C’est ce qui rend Angular séduisant, ce coté « magique ».
C’est la marche suivante, passer l’effet two ways databinding, qui peut faire trébucher un projet, notamment quand la notion de scope apparaît et à partir du moment où le projet demande plus que des petits composants simples de base.
L’ecosystème JS et notamment les composants (et non pas les frameworks) est extrêmement riche. Trouver des composants de qualités pose rarement de problèmes.
Angular couche mal avec ces composants et les composants angular de qualité ne sont pas légions. D’où la création de directive (avec la complexité associée) pour interfacer des composants externes, créer des composants from scratch ou adapter des composants existant. Le tout au risque de réinventer la roue mais en faisant moins bien.
+1 pour la partie SEO, un .htacess + un rendu coté serveur et le tour est joué
J’ai quand même tendance à penser qu’angularJs est la solution à de nombreux cas d’utilisation. J’ai fait 3 grosses applications avec AngularJs, très différentes les unes des autres (pas que des back office) et à chaque fois je pensais « changement d’état » et angularjs me permettait d’être très productif et très propre. Jamais je ne retrournerai au $(« #… »).append ou je ne sais quelle manipulation du DOM. Jamais je n’avais l’impression de tordre mon application pour qu’elle collent à « the angular way », au contraire ca faisait du code plus simple, plus expressif, plus en phase avec les actions utilisateur.
La courbe d’apprentissage est très progressive, et il n’y a pas besoin d’1 mois de pratique pour faire des routes, des formulaires et des listes. Quant aux directives elles se découvrent dès qu’on se rend compte qu’on se répêtent et/ou qu’on voudrait manipuler le DOM dans les controleurs. Ca n’a rien de compliqué (pas besoin d’aller fouiller les finesses du $compile ou $interpolate).
Ce qui manque un peu c’est quelques mises en garde afin de ne pas écrire du code qui mettait les navigateurs à genoux. Dans la console par exemple, il pourrait y avoir des warning lorsque les bindings sont trop nombreux.
Quant à la page qui s’affiche et les données qui viennent après, un div d’attente pleine page est facile à mettre en oeuvre (via une directive par exemple : et un scope.$watch(attrs.dependsOn, function(o){ if(o){element.hide()}});
Mais avec des appels REST et un cache HTTP en frontal de l’API rest (et échanges de last-modified / if-modified-since / HTTP 304) on arrive à des échanges très rapides.
Par contre je serai curieux d’en savoir plus sur les rendus côté serveur. C’est pas pour rien que Github et Twitter font ça.
Les chevrons ne passent pas…
via une directive par exemple : wait depends-on= »myObject »