<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2500483166927903911</id><updated>2025-08-05T09:35:58.997+02:00</updated><category term="service"/><category term="directive"/><category term="$scope"/><category term="contrôleur"/><category term="évènement"/><category term="$watch"/><category term="injection de dépendances"/><category term="module"/><category term="promise"/><category term="$http"/><category term="provider"/><category term="$compile"/><category term="$q"/><category term="$resource"/><category term="$route"/><category term="Drupal"/><category term="apprentissage"/><category term="architecture"/><category term="backend"/><category term="clavier"/><category term="communauté"/><category term="drag and drop"/><category term="expression"/><category term="formulaire"/><category term="graphique"/><category term="internationalisation"/><category term="ng-repeat"/><category term="pagination"/><category term="performances"/><category term="test"/><category term="validation"/><title type='text'>FrAngular : AngularJS en français</title><subtitle type='html'>Blog francophone sur le framework AngularJS</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.frangular.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://www.frangular.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>28</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-4561612200641997546</id><published>2014-02-21T22:45:00.000+01:00</published><updated>2014-02-21T22:49:29.256+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="backend"/><category scheme="http://www.blogger.com/atom/ns#" term="Drupal"/><title type='text'>Drupal + AngularJS : l&#39;ultime solution de theming pour Drupal ? </title><content type='html'>Customiser le code HTML créé par &lt;b&gt;Drupal &lt;/b&gt;et ses modules est sans doute l&#39;un des points les plus difficiles à comprendre. Les solutions proposées pour tenter de faciliter cette tâche sont nombreuses&amp;nbsp;: Starter thèmes, modules type Panels ou&amp;nbsp; Display Suite, modules d&#39;intégration de bibliothèques JavaScript, la liste est infinie. On passe beaucoup de temps dans la création d&#39;un site sous Drupal à tenter de livrer un code HTML qui conviendra à l&#39;intégrateur, qui de toute façon s&#39;arrachera les cheveux à un moment ou un autre.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;AngularJS &lt;/b&gt;est un framework Javascript qui adapte et étend le HTML grâce à des directives qu&#39;on y inclut. Mes premiers pas avec AngularJS m&#39;ont donné à penser qu&#39;il pourrait bien être complémentaire de Drupal. Parlons de ces premiers pas justement, la communauté AngularJS propose un tutoriel pour débuter qui permet de créer une application &lt;a href=&quot;http://docs.angularjs.org/tutorial/&quot;&gt;Phonecat&lt;/a&gt; qui affiche des informations techniques à propos de téléphones portables.&lt;br /&gt;
&lt;br /&gt;
Je vous propose donc de modifier cette application pour qu&#39;elle récupère ses informations depuis Drupal via des web services. Drupal servirait alors uniquement de backoffice à l&#39;application AngularJS, chose que ce CMS fait très bien.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Fonctionnement&lt;/h2&gt;
&lt;br /&gt;
L&#39;idée est donc la suivante&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-GEohFa4DDx7lNipsFwreNBgq4urztxIafHK4-N3ZP345gh4r0MM-e8k4XDGSgR-cj-VZNcAjf0TSBnsq9YIN63lI5kBZIxmQVFNlShcvh7ztiE_A7I6WN5rgOuNUGtM8uNCjyuZnQ78/s1600/img1.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-GEohFa4DDx7lNipsFwreNBgq4urztxIafHK4-N3ZP345gh4r0MM-e8k4XDGSgR-cj-VZNcAjf0TSBnsq9YIN63lI5kBZIxmQVFNlShcvh7ztiE_A7I6WN5rgOuNUGtM8uNCjyuZnQ78/s1600/img1.png&quot; height=&quot;89&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
A partir de Drupal je vais créer un nouveau type de contenu classiquement qui générera des nodes. La liste de ces nodes sera créée via le module Views. Ensuite, la vue et les nodes seront exposés via des webservices REST grâce au module &lt;a href=&quot;https://drupal.org/project/services&quot;&gt;Services&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Côté client, j&#39;utiliserai $resource d&#39;AngularJS pour les accès à ces services REST, et les données pourront être exploitées par les contrôleurs et présentées par les templates&amp;nbsp; de l&#39;application.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Installation de Drupal et de Phonecat AngularJS&lt;/h2&gt;
&lt;br /&gt;
J&#39;ai décidé de positionner l&#39;application AngularJS à la racine de mon répertoire et d&#39;avoir un sous-dossier Drupal dans ce répertoire. Voici l&#39;arborescence que j&#39;ai obtenue après installation&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC_cBt8brAJm8pwxVOWhcGuOOw3-pZN5ExZGCTasWHbqz1OPaTlMVvhKGzPK3umc-qBf_WDlgbktSbNNUSRsiKG7a452Nf404EyLaO7E_4TsEO1Og_oZkLgQpFojMuY_F8hr8Gho34gko/s1600/arborescence.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC_cBt8brAJm8pwxVOWhcGuOOw3-pZN5ExZGCTasWHbqz1OPaTlMVvhKGzPK3umc-qBf_WDlgbktSbNNUSRsiKG7a452Nf404EyLaO7E_4TsEO1Og_oZkLgQpFojMuY_F8hr8Gho34gko/s1600/arborescence.png&quot; height=&quot;320&quot; width=&quot;211&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h3&gt;
Créer un virtualhost ng-drupal&lt;/h3&gt;
J&#39;ai commencé par créer un nouveau virtual host dans Apache qui mène à mon répertoire Drupal/Angular. Voici mon ng-drupal.conf&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt; 
&amp;nbsp; DocumentRoot /home/desktop/s/ng-drupal 
&amp;nbsp; ServerName ng-drupal.tld 
&amp;nbsp; &amp;lt;Directory /home/desktop/s/ng-drupal&amp;gt; 
&amp;nbsp;&amp;nbsp;&amp;nbsp; AllowOverride all 
&amp;nbsp; &amp;lt;/Directory&amp;gt; 
&amp;lt;/VirtualHost&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h3&gt;
Cloner le tutorial Phonecat / Angular&lt;/h3&gt;
Je suis parti du tutorial officiel proposé sur le site officiel AngularJS. Si vous ne connaissez pas ce framework, je vous encourage à le lire dans son ensemble. &lt;br /&gt;
&lt;br /&gt;
Placez-vous à la racine du répertoire précédemment créé et exécutez les deux commandes suivantes. La première permet de copier le code source du tutoriel et la seconde de se placer à la dernière étape.&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;git clone https://github.com/angular/angular-phonecat.git .
git checkout -f step-12&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
A l&#39;adresse suivante, j&#39;ai maintenant le tutoriel qui s&#39;affiche&amp;nbsp;:&lt;br /&gt;
http://ng-drupal.tld/app/&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLnYBEKn1ryB2OB9Uny6b8cP6GzDfjEV0Nzz5N0eSce-3WLpJR4oc49M2EQHKRDLIIPGwz1qqDvayVI8nm2vQu3Kd9wCPPBRs7trTICK9Qyiv8Q3RuUzw9idsjZac3157JppKHyFRaCRI/s1600/liste.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLnYBEKn1ryB2OB9Uny6b8cP6GzDfjEV0Nzz5N0eSce-3WLpJR4oc49M2EQHKRDLIIPGwz1qqDvayVI8nm2vQu3Kd9wCPPBRs7trTICK9Qyiv8Q3RuUzw9idsjZac3157JppKHyFRaCRI/s1600/liste.png&quot; height=&quot;277&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h3&gt;
Installation de Drupal&lt;/h3&gt;
J&#39;ai ensuite installé Drupal 7 dans le sous-répertoire /drupal du dossier principal. Si vous ne connaissez pas ce CMS, vous pouvez utiliser mon livre librement téléchargeable chez Framabook.&lt;br /&gt;
&lt;br /&gt;
Pour accéder à l&#39;installateur, j&#39;ai lancé cette url&amp;nbsp;: http://ng-drupal.tld/drupal&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-UfSsKGHhFSSnBVHkvsss7EznBooRwN3N3G_wwcrfyLb5USTAHG__2CA1UXkjozKUMDEGyzUK1psgO6Aj-SSVpy-vLdFHVwWxp5KMZ5Ph7OZfsDGBI46yk0_r0ttVsU9TcOrsEY0lzik/s1600/drupal.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-UfSsKGHhFSSnBVHkvsss7EznBooRwN3N3G_wwcrfyLb5USTAHG__2CA1UXkjozKUMDEGyzUK1psgO6Aj-SSVpy-vLdFHVwWxp5KMZ5Ph7OZfsDGBI46yk0_r0ttVsU9TcOrsEY0lzik/s1600/drupal.png&quot; height=&quot;277&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Après quelques minutes, Drupal était installé :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Installation de la Feature angular-phonecat&lt;/h3&gt;
Pour vous éviter de longues configurations et saisies, j&#39;ai créé une «&amp;nbsp;Feature&amp;nbsp;» (module) qui contient les éléments Drupal qui permettent de créer les webservices pour l&#39;application Phonecat. Elle contient&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;un type de contenu phone avec tous les champs des téléphones du tutorial&lt;/li&gt;
&lt;li&gt;du contenu qui utilise le type de contenu Phone&lt;/li&gt;
&lt;li&gt;une vue qui liste tous les téléphones du site&lt;/li&gt;
&lt;li&gt;un service qui permettent d&#39;exporter sous forme de JSON la vue ou un élément du contenu&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
J&#39;ai donc installé cette Feature dans le répertoire drupal/sites/default/modules (à créer si il n&#39;existe pas).&lt;br /&gt;
Vous pouvez télécharger cette feature &lt;a href=&quot;http://www.atelierdrupal.net/angular_phonecat-7.x-0.10.tar&quot;&gt;ici&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYQ2TNvMk7O3-24YIvDwNbhyphenhyphenypQVsQckxkEHUyedS0H3z8j5TvscL61jfNNuSICG9_RVq7uL8S5eQCnXWQdVPtC-V0bEG2-S20EOHI-BFB1GLtZIiOYhhReetcs5D51CqJsAHFrBAOFPs/s1600/feature.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYQ2TNvMk7O3-24YIvDwNbhyphenhyphenypQVsQckxkEHUyedS0H3z8j5TvscL61jfNNuSICG9_RVq7uL8S5eQCnXWQdVPtC-V0bEG2-S20EOHI-BFB1GLtZIiOYhhReetcs5D51CqJsAHFrBAOFPs/s1600/feature.png&quot; height=&quot;141&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h3&gt;
Installation des dépendances&lt;/h3&gt;
Ce module a des dépendances manquantes, je les ai installées&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Ctools&amp;nbsp;: boîte à outils utilisée par un grand nombre de modules Drupal&lt;/li&gt;
&lt;li&gt;Features&amp;nbsp;: permet de créer des fonctionnalités sous forme de module&lt;/li&gt;
&lt;li&gt;Field_group&amp;nbsp;: utilisé pour regrouper les nombreux champs du contenu phone&lt;/li&gt;
&lt;li&gt;Node_export_features&amp;nbsp;: permet d&#39;inclure du contenu dans une feature&lt;/li&gt;
&lt;li&gt;Universally Unique ID&amp;nbsp;: module permettant d&#39;attribuer un id unique à chaque contenu&lt;/li&gt;
&lt;li&gt;Services&amp;nbsp;: permet à Drupal de créer des webservices&lt;/li&gt;
&lt;li&gt;Views&amp;nbsp;: permet d&#39;extraire différents types de données de Drupal et de les présenter&lt;/li&gt;
&lt;li&gt;Libraries&amp;nbsp;: permet aux modules Drupal d&#39;utiliser des bibliothèques externes&lt;/li&gt;
&lt;li&gt;REST Server&amp;nbsp;: permet de créer un serveur REST&lt;/li&gt;
&lt;li&gt;Services views&amp;nbsp;: permet à Services d&#39;exposer le résultat des vues&lt;/li&gt;
&lt;/ul&gt;
Pour plus de rapidité, j&#39;ai utilisé la commande Drush suivante&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;drush dl ctools features views services field_group node_export uuid libraries services_views&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
J&#39;ai ensuite pu activer les modules et ma feature. J&#39;ai dû m&#39;y reprendre à deux fois car mes contenus ont été importés avant mon type de contenu.&lt;br /&gt;
&lt;br /&gt;
L&#39;installation coté Drupal est terminée&amp;nbsp;! &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Les contenus dans Drupal&lt;/h2&gt;
&lt;br /&gt;
Vous avez maintenant la possibilité d&#39;ajouter de nouveaux téléphones graphiquement avec Drupal&amp;nbsp;:&lt;br /&gt;
Menu Content – Lien Add content – Phone&lt;br /&gt;
&lt;br /&gt;
Voici l&#39;interface que j&#39;ai utilisée pour ajouter les quatre téléphones de la Feature que j&#39;ai créée.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpGvsBScuKAWszg0LjtwL8zfkKTiCrX3CrqPYmmzf9emTPD2hzT6uwd-faiSGIiJSxiD_uIJ0NAI90y4Ydphmz6aTSEznNVD5fRvOtJ60uB1gqfgWMkBjc5xI3GsjAPUPI0TUHazOIfGc/s1600/admin.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpGvsBScuKAWszg0LjtwL8zfkKTiCrX3CrqPYmmzf9emTPD2hzT6uwd-faiSGIiJSxiD_uIJ0NAI90y4Ydphmz6aTSEznNVD5fRvOtJ60uB1gqfgWMkBjc5xI3GsjAPUPI0TUHazOIfGc/s1600/admin.png&quot; height=&quot;237&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Pour ceux qui connaissent mal Drupal, sachez que vous pouvez créer ce type de formulaire graphiquement dans Drupal en choisissant les champs qui les composent. &lt;br /&gt;
&lt;br /&gt;
Vous pouvez modifier le type de contenu Phone (Menu &lt;b&gt;Structure – Content types&lt;/b&gt;, puis lien &lt;b&gt;manage fields&lt;/b&gt; de Phone)&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSLJiGAo0SXjYQraeotiV5Di__caGP87Zdca8z5qPInt-O5zwVgEkVun9j8a6IUqc3XODo-XWqEqYrP27lwLgDvt_SYI9XwgUH4eVTVdsZj6pfOeteOwZ-eSvwWrOtmOc6rbrwIQDLfkI/s1600/admin2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSLJiGAo0SXjYQraeotiV5Di__caGP87Zdca8z5qPInt-O5zwVgEkVun9j8a6IUqc3XODo-XWqEqYrP27lwLgDvt_SYI9XwgUH4eVTVdsZj6pfOeteOwZ-eSvwWrOtmOc6rbrwIQDLfkI/s1600/admin2.png&quot; height=&quot;237&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
La page de liste&lt;/h2&gt;
J&#39;ai commencé par créer la page de liste des téléphones.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjljt9NbF2sqPeLZITfShjPfBAIPxnfarhTpwdpxamRYYZ-CUxacpF7RPKXOPd7tpH8ZJo6U8Btyep-HwBiiWcPHDY1K4aLfY8wGYBvjs8qVC0oFnmIVUH08xhuqNIQAPMVujDlq3kmqlY/s1600/liste2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjljt9NbF2sqPeLZITfShjPfBAIPxnfarhTpwdpxamRYYZ-CUxacpF7RPKXOPd7tpH8ZJo6U8Btyep-HwBiiWcPHDY1K4aLfY8wGYBvjs8qVC0oFnmIVUH08xhuqNIQAPMVujDlq3kmqlY/s1600/liste2.png&quot; height=&quot;185&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
La vue dans Drupal&lt;/h3&gt;
Cette liste utilise 4 champs pour chaque téléphone&amp;nbsp;:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Le titre&lt;/li&gt;
&lt;li&gt;La description&lt;/li&gt;
&lt;li&gt;Le nid (pour créer un lien vers le détail d&#39;un téléphone)&lt;/li&gt;
&lt;li&gt;L&#39;image&lt;/li&gt;
&lt;/ul&gt;
J&#39;ai donc créé une vue Phone-list dans Drupal (menu &lt;b&gt;Structure – Views&lt;/b&gt;, puis lien &lt;b&gt;Edit&lt;/b&gt; de la vue &lt;b&gt;Phone-list&lt;/b&gt;)&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIcM3tWHJYKf2xYFHPQM8yW-xC1BuKf7O8dLpU7xaEoobwPr-RU14JBTmWLkK-Wola5szXkZjC2ON_CcqKTzDB_UT8DvQF1BQImjyeNBSXvxBKHVeNHonN4o1ws2a651q6MFGLZV-ZJyw/s1600/views.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIcM3tWHJYKf2xYFHPQM8yW-xC1BuKf7O8dLpU7xaEoobwPr-RU14JBTmWLkK-Wola5szXkZjC2ON_CcqKTzDB_UT8DvQF1BQImjyeNBSXvxBKHVeNHonN4o1ws2a651q6MFGLZV-ZJyw/s1600/views.png&quot; height=&quot;257&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Notez que j&#39;ai utilisé le redimensionnement en 100x100 pour l&#39;image, mais j&#39;aurais pu en créer une autre facilement.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Le webservice dans Drupal&lt;/h3&gt;
Ensuite, pour exporter cette vue en JSON, j&#39;ai utilisé le module services (menu Structure – Services, puis lien Edit Resources du service phones). &lt;br /&gt;
&lt;br /&gt;
J&#39;ai indiqué que le chemin à utiliser pour accéder au service sera phones (onglet Edit) et que ca sera un serveur REST. J&#39;ai autorisé le webservice à accéder à la lecture des nodes pour le détail des téléphones et aux vues pour accéder à celle que je viens de créer&amp;nbsp;: Phone-list (onglet Ressources).&lt;br /&gt;
&lt;br /&gt;
Voici le chemin complet à utiliser pour accéder à une vue via un webservice&amp;nbsp;:&lt;br /&gt;
http://example.com/&amp;lt;endpoint path&amp;gt;/views/&amp;lt;view name&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ce qui m&#39;a donné&amp;nbsp;:&lt;br /&gt;
http://ng-drupal.tld/drupal/phones/views/phone_list.json&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5kPwTCl7huf3WQJFxfUmxm1lfjDmVxyZYjMfnQ-ATf7nxUSDjUxoYoCoXH-jAYjiiKYMo4wQmZYsMm_W956_HWNP8JT2mgBBG92Hh-NVNKoXC8f717_KF2ImmU7XxNLgzp39ZnXHL4XU/s1600/json.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5kPwTCl7huf3WQJFxfUmxm1lfjDmVxyZYjMfnQ-ATf7nxUSDjUxoYoCoXH-jAYjiiKYMo4wQmZYsMm_W956_HWNP8JT2mgBBG92Hh-NVNKoXC8f717_KF2ImmU7XxNLgzp39ZnXHL4XU/s1600/json.png&quot; height=&quot;145&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h3&gt;
Récupération dans Angularjs de phone_list.json&lt;/h3&gt;
Au terme de ce tutoriel, j&#39;aurai donc deux services&amp;nbsp;: un basé sur Views qui me permettra de récupérer la liste des téléphones, un autre sur Nodes qui me permettra de récupérer un seul téléphone.&lt;br /&gt;
&lt;br /&gt;
J&#39;ai donc commencé par créer le premier service, dans le fichier services.js de l&#39;application Phonecat&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;phonecatServices.factory(&#39;Phones&#39;, [&#39;$resource&#39;,
&amp;nbsp; function($resource){
&amp;nbsp;&amp;nbsp;&amp;nbsp; return $resource(&#39;http://ng-drupal.tld/drupal/phones/views/phone_list.json&#39;, {}, {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; query: {method:&#39;GET&#39;, isArray:true}
&amp;nbsp;&amp;nbsp;&amp;nbsp; });
&amp;nbsp; }]);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
J&#39;ai utilisé ce service avec le controller PhoneListCtrl dans le fichier controllers.js&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;phonecatControllers.controller(&#39;PhoneListCtrl&#39;, [&#39;$scope&#39;, &#39;Phones&#39;,
&amp;nbsp; function($scope, Phones) {
&amp;nbsp;&amp;nbsp;&amp;nbsp; $scope.phones = Phones.query();
&amp;nbsp;&amp;nbsp;&amp;nbsp; $scope.orderProp = &#39;age&#39;;
&amp;nbsp; }]);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Grâce à &lt;a href=&quot;https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk&quot;&gt;Batarang&lt;/a&gt;, je vois que le JSON est bien chargé.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTDwrQkskYhHXi6IpA4Gm6I98lYtPcIPb99m3SJNdnxgRgsjc6RGv7AG6O9MYpxf2fly61VUBTNUnqstwgUlf6SFdrMxMU7CqEYKdvdKj6omUA7PJFId3T6IhwkuLtG11TZDA-a3e_qZA/s1600/batarang.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTDwrQkskYhHXi6IpA4Gm6I98lYtPcIPb99m3SJNdnxgRgsjc6RGv7AG6O9MYpxf2fly61VUBTNUnqstwgUlf6SFdrMxMU7CqEYKdvdKj6omUA7PJFId3T6IhwkuLtG11TZDA-a3e_qZA/s1600/batarang.png&quot; height=&quot;288&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h3&gt;
Affichage dans le template&lt;/h3&gt;
Il ne reste donc qu&#39;à charger les bons éléments dans le template&amp;nbsp; partials/phone-list.html&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Titre et description :&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Les champs s&#39;appellent maintenant node_title, nid et description&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;#/phones/{{phone.nid}}&quot;&amp;gt;{{phone.node_title}}&amp;lt;/a&amp;gt;
&amp;lt;p&amp;gt;{{phone.description}}&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;Image :&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Pour le champ image, j&#39;injecte directement le html provenant du JSON grâce à la directive &lt;a href=&quot;http://docs.angularjs.org/api/ng.directive:ngBindHtml&quot;&gt;ng-bind-html&lt;/a&gt;. Pour qu&#39;elle fonctionne, il me faut ajouter la dépendance ngSanitize à mon module phonecatService dans services.js&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var phonecatServices = angular.module(&#39;phonecatServices&#39;, [&#39;ngResource&#39;, &#39;ngSanitize&#39;]);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Je dois également ajouter le script angular-sanitize.js dans index.html&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;lib/angular/angular-sanitize.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
puis, je complète mon fichier phone-list.html pour l&#39;affichage des images&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;#/phones/{{phone.nid}}&quot; class=&quot;thumb&quot; ng-bind-html=&quot;phone.images&quot;&amp;gt;&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h3&gt;
Filtres&lt;/h3&gt;
Tout est bien chargé, il ne me reste plus qu&#39;à faire fonctionner les filtres en changeant les noms des champs.&lt;br /&gt;
&lt;br /&gt;
Dans partials/pḧone-list.html&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;option value=&quot;node_title&quot;&amp;gt;Alphabetical&amp;lt;/option&amp;gt; 
&amp;lt;option value=&quot;nid&quot;&amp;gt;Newest&amp;lt;/option&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Dans controller.js&amp;nbsp;, indiquer la valeur par défaut (nid) :&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$scope.orderProp = &#39;node_title&#39;;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
La liste des téléphones fonctionne maintenant&amp;nbsp;! Finalement, je n&#39;ai eu à changer que les noms de champs&amp;nbsp;!&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Page détail&lt;/h2&gt;
&lt;h3&gt;
Création du service dans Drupal&lt;/h3&gt;
Rien à faire ou presque pour cette page détail coté Drupal puisque le module Service fournit par défaut un moyen de récupérer le JSON d&#39;un node. Il m&#39;a suffit pour cela de cocher le service node/retrieve pour que mon webservice fonctionne avec l&#39;adresse&amp;nbsp;:&lt;br /&gt;
http://ng-drupal.tld/drupal/phones/node/1.json&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3FaF0kAzI2L8GybY1w1tT7uY_QiX7-_r3QH6AZZ4RHHskwh8rZZSXONeDpxiUiflBuTF0WM45PjtLxYvJ76clcLUrGpPnSgzw30SxThtMKYAPCqBucca10VPgHORGubA-1sF4Q_XQS1k/s1600/service.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3FaF0kAzI2L8GybY1w1tT7uY_QiX7-_r3QH6AZZ4RHHskwh8rZZSXONeDpxiUiflBuTF0WM45PjtLxYvJ76clcLUrGpPnSgzw30SxThtMKYAPCqBucca10VPgHORGubA-1sF4Q_XQS1k/s1600/service.png&quot; height=&quot;311&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeYK30H4dnRPWB-xI6YE1C9NdfcDjg3I4acqK_ffKnU61BBaXbBoDBWS_l7us0G_Cur-1T2vKvrdoFvdAtkJCTmkn8o5ba5vwAIyuqwemFJ8A9eGUYPYlpDIPrZL_oW9Vu6JJcJSBN0VQ/s1600/json2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeYK30H4dnRPWB-xI6YE1C9NdfcDjg3I4acqK_ffKnU61BBaXbBoDBWS_l7us0G_Cur-1T2vKvrdoFvdAtkJCTmkn8o5ba5vwAIyuqwemFJ8A9eGUYPYlpDIPrZL_oW9Vu6JJcJSBN0VQ/s1600/json2.png&quot; height=&quot;185&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h3&gt;
Modification du service dans AngularJS&lt;/h3&gt;
Coté AngularJS, il faut modifier l&#39;url de la ressource Phone :&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;phonecatServices.factory(&#39;Phone&#39;, [&#39;$resource&#39;,
&amp;nbsp; function($resource){
&amp;nbsp;&amp;nbsp;&amp;nbsp; return $resource(&#39;http://ng-drupal.tld/drupal/phones/node/:phoneId.json&#39;, {}, {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; query: {method:&#39;GET&#39;, params:{phoneId:&#39;phones&#39;}, isArray:true}
&amp;nbsp;&amp;nbsp;&amp;nbsp; });
&amp;nbsp; }]);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
L&#39;argument n&#39;est donc plus le nom du téléphone, mais le nid drupal du node.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-yT0WLifMPweNjELZzVUs_-OA608YDmYI3ClEjTxmmorM-85pdGAuKiDVnqkmtrKg87aHKtET_KkSBuhtd1c9KWLR7EaATFH8k08nTt_zXQjjPCzK-08wBk3gbo9bNdbQaT5sVL1xV6A/s1600/batarang2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-yT0WLifMPweNjELZzVUs_-OA608YDmYI3ClEjTxmmorM-85pdGAuKiDVnqkmtrKg87aHKtET_KkSBuhtd1c9KWLR7EaATFH8k08nTt_zXQjjPCzK-08wBk3gbo9bNdbQaT5sVL1xV6A/s1600/batarang2.png&quot; height=&quot;237&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Toutes les données sont chargées, il faut maintenant les afficher&amp;nbsp;!&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Modification du phone-detail.html avec le json de Drupal&lt;/h3&gt;
J&#39;ai eu un travail assez long pour récupérer les valeurs intéressantes dans le JSON généré par Drupal avec l&#39;application Angular. Il a fallu gérer quatre types de données &amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;
Type texte simple :&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Par exemple, le champ RAM est affiché comme cela à l&#39;origine&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;{{phone.storage.ram}}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Il devient alors&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;{{phone.field_ram.und[0].value}}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
«&amp;nbsp;und&amp;nbsp;» est bien connu des drupaliens et veut dire undefined, c&#39;est à dire que la langue n&#39;est pas définie pour ce champ. 0 est l&#39;indice du champ en cas de valeur multiple&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Type texte multiple :&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#39;avoir un champ qui contient un nombre non défini de valeurs sous Drupal. C&#39;est par exemple le cas du champ dimensions.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguSuDbN2SMYJWiK1gU3YksdoO2G9Bx2Y_EldVJEsGopch9QUUj-KHYNFkUBAvUzZp201UVKB1JrL3n8x8zQ5nk8AFQaOLV58Cybf22XHTwSBZvb-nJdeXQkYeRiad_O_6cnBaJAnnvrE4/s1600/champ_multiple.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguSuDbN2SMYJWiK1gU3YksdoO2G9Bx2Y_EldVJEsGopch9QUUj-KHYNFkUBAvUzZp201UVKB1JrL3n8x8zQ5nk8AFQaOLV58Cybf22XHTwSBZvb-nJdeXQkYeRiad_O_6cnBaJAnnvrE4/s1600/champ_multiple.png&quot; height=&quot;215&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
J&#39;ai donc dû boucler sur le tableau phone.field_dimensions.und pour récupérer et afficher chaque valeur&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;dd ng-repeat=&quot;dim in phone.field_dimensions.und&quot;&amp;gt;{{dim.value}}&amp;lt;/dd&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;Type booléen :&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
A l&#39;étape n°9 du tutoriel Angular, un filtre checkmark est ajouté. Il évalue si la valeur est à true ou false alors que Drupal retourne 0 ou 1. J&#39;ai donc dû changer la règle du filtre&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;return input == 1 ? &#39;\u2713&#39; : &#39;\u2718&#39;;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Et afficher le résultat ainsi&amp;nbsp;:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;{{phone.field_infrared.und[0].value | checkmark}}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;
Les images :&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
J&#39;ai rencontré un petit souci pour afficher les images car les données du json ne fournissaient qu&#39;un lien vers une url Drupal (public://). &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Conclusion&lt;/h2&gt;
&lt;br /&gt;
Après la rédaction de ce tutoriel, je n&#39;ai pas de réponse pour savoir si la cohabitation entre Drupal et AngularJS est possible. J&#39;ai trouvé des solutions à tous les problèmes qui se sont posés à moi et j&#39;ai pu entrevoir le potentiel d&#39;AngularJS&amp;nbsp;. Cela m&#39;a donné l&#39;envie d&#39;aller plus loin&amp;nbsp;:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Comment fonctionnerait une authentification ?&lt;/li&gt;
&lt;li&gt;Peut-être aurait-il été encore plus facile et versatile de créer un petit module Drupal utilisant la fonction drupal_json_encode (lien?)&lt;/li&gt;
&lt;li&gt;L&#39;initiative développée pour Drupal 8 (https://groups.drupal.org/wscci) facilitera-t-elle ce type de développement&amp;nbsp;?&lt;/li&gt;
&lt;/ul&gt;
N&#39;hésitez pas à partager vos réflexions sur le sujet. Si vous avez un projet à développer et que vous aimeriez utiliser Drupal et AngularJS, n&#39;hésitez pas à me contacter&amp;nbsp;!&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://www.atelierdrupal.net/app.zip&quot;&gt;Téléchargez le dossier app de l&#39;application modifiée&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/4561612200641997546/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2014/02/drupal-angularjs-theming.html#comment-form' title='5 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4561612200641997546'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4561612200641997546'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2014/02/drupal-angularjs-theming.html' title='Drupal + AngularJS : l&#39;ultime solution de theming pour Drupal ? '/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/09396515025729360716</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-GEohFa4DDx7lNipsFwreNBgq4urztxIafHK4-N3ZP345gh4r0MM-e8k4XDGSgR-cj-VZNcAjf0TSBnsq9YIN63lI5kBZIxmQVFNlShcvh7ztiE_A7I6WN5rgOuNUGtM8uNCjyuZnQ78/s72-c/img1.png" height="72" width="72"/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-5966303701062572993</id><published>2013-10-31T01:25:00.000+01:00</published><updated>2013-11-10T22:31:47.204+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$scope"/><category scheme="http://www.blogger.com/atom/ns#" term="directive"/><title type='text'>Scope isolé dans les directives AngularJS</title><content type='html'>&lt;br /&gt;
Second sujet que j&#39;ai traité au Meetup chez Google, il s&#39;agit des scopes isolés dans les directives. &amp;nbsp;&lt;a href=&quot;http://tchatel.github.io/slides-ngParis-octobre2013/&quot; target=&quot;_blank&quot;&gt;Les slides se trouvent ici&lt;/a&gt;, et&amp;nbsp;&lt;a href=&quot;http://goo.gl/K2Stjk&quot;&gt;une vidéo a été mise en ligne là&lt;/a&gt;&amp;nbsp;; mon intervention commence par un sujet sur l&#39;usage des services, et celui-ci est à la suite.&lt;br /&gt;
&lt;br /&gt;
Le scope isolé est un outil bien pratique mis à notre disposition par AngularJS pour faciliter la création de widgets. Mais il ne faut pas l&#39;utiliser n&#39;importe quand, ni en faire n&#39;importe quoi.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
L&#39;arbre des scopes&lt;/h2&gt;
&lt;br /&gt;
Les scopes d&#39;AngularJS sont des objets qui servent de contexte d&#39;évaluation des expressions contenues dans les templates.&lt;br /&gt;
&lt;br /&gt;
Ils forment un arbre, dont la racine est le seul scope de l&#39;application qui est aussi publié comme un service, sous le nom &lt;i&gt;&lt;b&gt;$rootScope&lt;/b&gt;&lt;/i&gt;. Ce &lt;i&gt;$rootScope&lt;/i&gt; est associé à l&#39;élément contenant toute l&#39;application AngularJS, celui sur lequel on met la directive &lt;i&gt;ngApp &lt;/i&gt;: ça peut être l&#39;élément &amp;lt;html&amp;gt; lui-même, ou le &amp;lt;body&amp;gt;, ou un &amp;lt;div&amp;gt; à l&#39;intérieur, peu importe.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSKe5L5-omRD_87IXbLRHvT_ZAnuhV8heluffgMrL8qsDOVMBOCVBz7tEFPDcgvCzAj_BFyH7oJnQC1socDgtUf4TRUP5X3pQMCyfWQ6c4ho6oianL-O62Oaf2UEf93LmsYT99UvQ7uByk/s1600/scopes.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;316&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSKe5L5-omRD_87IXbLRHvT_ZAnuhV8heluffgMrL8qsDOVMBOCVBz7tEFPDcgvCzAj_BFyH7oJnQC1socDgtUf4TRUP5X3pQMCyfWQ6c4ho6oianL-O62Oaf2UEf93LmsYT99UvQ7uByk/s400/scopes.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
La structure arborescente des scopes est calquée sur celle des éléments de la page HTML. Chaque scope est lié à un élément du DOM, mais la réciproque n&#39;est pas vraie. Il n&#39;y a pas un scope pour chaque élément, seulement pour certains des éléments de la page HTML, parce qu&#39;on a placé sur ces éléments une directive AngularJS qui crée un nouveau scope. C&#39;est le cas des directives &lt;i&gt;ngController&lt;/i&gt;, &lt;i&gt;ngView&lt;/i&gt;, &lt;i&gt;ngRepeat&lt;/i&gt;, etc.&lt;br /&gt;
&lt;br /&gt;
Quand une directive crée un scope sur un élément HTML, ce scope aura pour parent le premier scope rencontré en remontant les éléments HTML.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Scope d&#39;un élément&lt;/h2&gt;
&lt;br /&gt;
Quand on crée une directive, qui sera déclenchée sur un élément HTML soit en lui ajoutant un attribut soit par le nom de l&#39;élément lui-même, on fournit un objet de définition de la directive. Il y a trois options possibles quant à la propriété&amp;nbsp;&lt;i&gt;&lt;b&gt;scope&lt;/b&gt;&lt;/i&gt;&amp;nbsp;de cet objet &amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;false &lt;/b&gt;pour ne rien faire (c&#39;est la valeur par défaut si la propriété &lt;i&gt;scope &lt;/i&gt;est absente)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;true &lt;/b&gt;pour créer un scope enfant&lt;/li&gt;
&lt;li&gt;&lt;b&gt;{...}&lt;/b&gt;&amp;nbsp;(un objet JavaScript avec des propriétés) pour créer un scope isolé&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Le scope isolé tout comme le scope enfant a un scope parent, référencé par sa propriété &lt;i&gt;$parent&lt;/i&gt;. La différence c&#39;est l&#39;héritage : seul le scope enfant hérite des données de son scope parent, le scope isolé n&#39;en hérite pas, c&#39;est pour ça qu&#39;il est dit &lt;i&gt;&quot;isolé&quot;&lt;/i&gt;. Il ne s&#39;agit pas de gros sous, mais de l&#39;héritage par prototype de JavaScript. Quand l&#39;interpréteur JS ne trouve pas une propriété dans un objet, il va la chercher dans son prototype. Puis éventuellement dans le prototype du prototype, tant qu&#39;il n&#39;est pas arrivé au bout de la chaîne des prototypes, ou qu&#39;il n&#39;a pas trouvé la propriété.&lt;br /&gt;
&lt;br /&gt;
Le scope parent est positionné par AngularJS comme prototype de ses scopes enfants, mais pas des scopes isolés. Du coup un scope enfant hérite automatiquement des données de son scope parent, y compris celles que le parent a hérité de ses propres parents. Il est donc inutile d&#39;aller chercher des données explicitement dans le scope parent en utilisant la propriété &lt;i&gt;$parent&lt;/i&gt;, puisqu&#39;elles sont héritées. Au contraire, le scope isolé n&#39;héritera d&#39;aucune donnée, et on peut dans certains cas (assez rares) avoir besoin de récupérer son scope parent via sa propriété &lt;i&gt;$parent&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
Ce qu&#39;il est très important de comprendre, c&#39;est que même si c&#39;est au niveau d&#39;une directive qu&#39;on indique si elle doit créer un scope enfant ou un scope isolé, ce scope est créé pour l&#39;élément HTML. Ce sera dont le scope récupéré par &lt;b&gt;toutes les directives placées sur cet élément&lt;/b&gt;, et aussi celles placées sur ses éléments enfants s&#39;ils n&#39;ont pas eux-mêmes leur propre scope. Les trois premiers paramètres de la fonction &lt;i&gt;link&lt;/i&gt;&amp;nbsp;de la directive, en l&#39;occurrence le scope, l&#39;élément et l&#39;objet contenant les attributs, ne dépendent pas de la directive, et seront les mêmes pour toutes les directives du même élément. Ils ne dépendent que de l&#39;élément lui-même, ce qui fait que toutes les directives d&#39;un même élément reçoivent le même scope, celui de l&#39;élément, et tous les attributs de l&#39;élément HTML.&lt;br /&gt;
&lt;br /&gt;
Evidemment, ça ne peut fonctionner que si les directives d&#39;un élément HTML ne formulent pas de demandes contradictoires. Si une directive demande la création d&#39;un scope enfant, alors que les autres ne demandent rien (leur propriété &lt;i&gt;scope&lt;/i&gt;&amp;nbsp;est à &lt;i&gt;false&lt;/i&gt;), alors AngularJS crée un scope enfant pour cet élément, qui sera passé à toutes les directives, même celles qui n&#39;ont rien demandé. Mais il est impossible d&#39;avoir sur un même élément une directive demandant un scope enfant et une autre demandant un scope isolé, ou deux directives demandant un scope isolé, chacune avec sa propre définition. Dans ces cas d&#39;incompatibilité, le framework signale une erreur.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Quand et pourquoi un scope isolé ?&lt;/h2&gt;
&lt;br /&gt;
La plupart des directives n&#39;ont pas à créer de nouveau scope, que ce soit un scope enfant ou isolé. Quand une directive demande la création d&#39;un nouveau scope, ce n&#39;est pas anodin car ça la rend incompatible avec d&#39;autres directives.&lt;br /&gt;
&lt;br /&gt;
Si elle demande un scope isolé, c&#39;est encore plus contraignant, car ça veut dire qu&#39;aucune des données du scope parent ne sera disponible dans le scope de l&#39;élément sur lequel elle est appliquée. Ça signifie qu&#39;il ne sera pas possible d&#39;utiliser sur ce même élément ou sur n&#39;importe quel élément enfant une autre directive qui prend dans un attribut (généralement du nom de la directive) une expression à évaluer sur le scope. Vu que les données ne seront pas présentes dans le scope, l&#39;évaluation de l&#39;expression ne peut que mal se passer.&lt;br /&gt;
&lt;br /&gt;
Il faut limiter l&#39;usage des scopes isolés à des éléments sans contenu et sur lesquels on ne met pas d&#39;autres directives, du moins pas de directive comme &lt;i&gt;ngShow &lt;/i&gt;par exemple, dont l&#39;expression serait alors évaluée sur un scope isolé ne contenant pas les données. Bon le&amp;nbsp;&lt;i&gt;ngShow&lt;/i&gt;, on peut toujours le mettre sur un &amp;lt;div&amp;gt; englobant l&#39;élément, mais pour les directives se trouvant dans le contenu de l&#39;élément c&#39;est mort.&lt;br /&gt;
&lt;br /&gt;
L&#39;usage typique d&#39;un scope isolé, c&#39;est dans une directive qui crée un composant complet, un widget comme un agenda, une carte, un graphique.&lt;br /&gt;
&lt;br /&gt;
Alors s&#39;il y a de telles limitations à l&#39;usage d&#39;un scope isolé, pourquoi s&#39;en servir ? Parce que dans les cas où l&#39;on peut le faire, ça simplifie le code de la directive, et ça évite le risque d&#39;impacter involontairement les données du scope parent.&lt;br /&gt;
&lt;br /&gt;
On peut publier tout ce qu&#39;on veut dans le scope isolé, et on peut évaluer toutes les expressions qu&#39;on veut sur le scope isolé, tant qu&#39;on n&#39;accède pas explicitement à sa propriété &lt;i&gt;$parent&lt;/i&gt;, on est sûr qu&#39;il n&#39;y aura aucune modification - ni même lecture d&#39;ailleurs - des données du scope parent. Le scope isolé créé par une directive, c&#39;est un peu comme le scope local d&#39;une fonction. A l&#39;exécution d&#39;une fonction, en JavaScript ou dans n&#39;importe quel autre langage, les variables locales définies dans la fonction n&#39;existent qu&#39;à l&#39;intérieur de cette fonction, et il ne peut pas y avoir de collision avec des variables externes à la fonction.&lt;br /&gt;
&lt;br /&gt;
L&#39;analogie va plus loin : les propriétés définies dans le scope isolé vont s&#39;apparenter aux paramètres de la fonction. En définissant une propriété dans le scope isolé, on lui donne un nom local à la directive, enfin plus précisément dans le scope de l&#39;élément donc pour toutes les directives de cet élément s&#39;il y en a plusieurs. Dans la directive, on travaille alors uniquement avec les propriétés définies dans le scope isolé, sans aller chercher manuellement les valeurs des attributs de l&#39;élément.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Bindings des propriétés du scope isolé&lt;/h2&gt;
&lt;br /&gt;
Chaque propriété définie dans le scope isolé va être liée par un binding d&#39;un certain type à un attribut de l&#39;élément HTML.&lt;br /&gt;
&lt;br /&gt;
On va avoir trois utilisations différentes pour les attributs de l&#39;élément :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;des attributs de &lt;b&gt;type texte&lt;/b&gt;, qui peuvent éventuellement contenir une ou plusieurs expressions entre doubles accolades {{...}} (comme la directive &lt;i&gt;ngSrc&lt;/i&gt;)&lt;/li&gt;
&lt;li&gt;des attributs de &lt;b&gt;type expression &lt;/b&gt;qui servent à faire un binding sur la &lt;b&gt;valeur &lt;/b&gt;de l&#39;expression (comme la directive &lt;i&gt;ngBind&lt;/i&gt;)&lt;/li&gt;
&lt;li&gt;des attributs de &lt;b&gt;type expression &lt;/b&gt;qui servent à déclencher une &lt;b&gt;action &lt;/b&gt;(comme la directive &lt;i&gt;ngClick&lt;/i&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Ces trois cas d&#39;utilisation correspondent aux trois symboles utilisables dans la définition des propriétés du scope isolé :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&#39;&lt;b&gt;@&lt;/b&gt;&#39; pour un &lt;b&gt;attribut texte&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&#39;&lt;b&gt;=&lt;/b&gt;&#39; pour une &lt;b&gt;expression valeur&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&#39;&lt;b&gt;&amp;amp;&lt;/b&gt;&#39; pour une &lt;b&gt;expression action&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
@ : attribut texte&amp;nbsp;&lt;/h3&gt;
&lt;br /&gt;
On indique le nom de la propriété, et dans une chaîne de caractères le nom de l&#39;attribut de l&#39;élément HTML précédé par le signe @ :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope: { prop1: &#39;@attr1&#39; },&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
On peut aussi omettre le nom de l&#39;attribut s&#39;il est identique au nom de la propriété, dans ce cas on met seulement le signe &#39;@&#39;.&lt;br /&gt;
&lt;br /&gt;
Prenons un exemple concret :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    &amp;lt;person name=&quot;{{user.firstName}} {{user.lastName}}&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
et&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope: {
        name: &#39;@&#39;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Ici le scope isolé est défini avec une propriété &lt;i&gt;name&lt;/i&gt;, qui référence l&#39;attribut texte du même nom. AngularJS crée un binding monodirectionnel de l&#39;attribut vers la propriété du scope isolé.&lt;br /&gt;
&lt;br /&gt;
Ça veut dire que la propriété &lt;i&gt;name&lt;/i&gt;&amp;nbsp;du scope isolé recevra automatiquement la valeur texte de l&#39;attribut référencé, dans laquelle les éventuelles expressions entre {{...}} auront été évaluées. Ici le prénom de l&#39;utilisateur, un espace, puis son nom. Et ce binding est permanent, chaque fois que &lt;i&gt;user.firstName&lt;/i&gt;&amp;nbsp;ou &lt;i&gt;user.lastName&lt;/i&gt;&amp;nbsp;changera de valeur, celle de la propriété &lt;i&gt;name &lt;/i&gt;&amp;nbsp;du scope isolé sera recalculée. La réciproque n&#39;est pas vraie, le binding est monodirectionnel : si on modifiait à l&#39;intérieur de la directive la valeur de la propriété &lt;i&gt;name&lt;/i&gt;&amp;nbsp;du scope isolé, ça n&#39;aurait aucune conséquence sur&amp;nbsp;&lt;i&gt;user.firstName&lt;/i&gt;&amp;nbsp;ou&amp;nbsp;&lt;i&gt;user.lastName&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
Et bien sûr, l&#39;évaluation des expressions de l&#39;attribut se fait sur le scope parent, pas sur le scope isolé qui n&#39;a hérité d&#39;aucune donnée, et dans lequel ni &lt;i&gt;user.firstName &lt;/i&gt;ni &lt;i&gt;user.lastName&amp;nbsp;&lt;/i&gt;ne sont définis.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
= &amp;nbsp;: expression valeur&lt;/h3&gt;
&lt;br /&gt;
La syntaxe est la même, pour une propriété qui référence un attribut contenant une expression à utiliser comme une valeur, on fait précéder le nom de l&#39;attribut par le signe =, et on peut encore omettre le nom de l&#39;attribut et mettre un signe &#39;=&#39; seul s&#39;il correspond au nom de la propriété.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope: { prop2: &#39;=attr2&#39; },&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Un exemple :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    &amp;lt;person name=&quot;user.lastName&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
et&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope: {
        name: &#39;=&#39;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Ici on a défini dans le scope isolé une propriété &lt;i&gt;name&lt;/i&gt;, qui référence l&#39;attribut &lt;i&gt;name&lt;/i&gt;&amp;nbsp;contenant une expression, pour qu&#39;AngularJS crée un binding bidirectionnel entre la propriété du scope isolé et la &lt;b&gt;valeur &lt;/b&gt;de cette expression (toujours évaluée sur le scope parent).&lt;br /&gt;
&lt;br /&gt;
Ça signifie ici que la propriété &lt;i&gt;name&lt;/i&gt;&amp;nbsp;contiendra toujours la valeur de l&#39;expression indiquée dans l&#39;attribut &lt;i&gt;name&lt;/i&gt;. Si la valeur de &lt;i&gt;user.lastName&lt;/i&gt;&amp;nbsp;est modifiée dans le scope parent, la propriété &lt;i&gt;name&lt;/i&gt;&amp;nbsp;du scope isolée sera recalculée. Et réciproquement, si dans le code de la directive on change la valeur de la propriété &lt;i&gt;name&lt;/i&gt;, alors AngularJS mettra à jour &lt;i&gt;user.lastName&lt;/i&gt;&amp;nbsp;dans le scope parent. C&#39;est plus simple que de devoir le gérer à la main.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
&amp;amp; &amp;nbsp;: expression action&lt;/h3&gt;
&lt;br /&gt;
De la même façon, pour définir une propriété référençant un attribut contenant une expression à utiliser comme une action, on fait précéder le nom de l&#39;attribut par le signe &lt;b&gt;&amp;amp;&lt;/b&gt;, et on peut là aussi omettre le nom de l&#39;attribut et mettre seulement le signe &#39;&lt;b&gt;&amp;amp;&lt;/b&gt;&#39; s&#39;il correspond au nom de la propriété.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope: { prop3: &#39;&amp;amp;attr3&#39; },&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Un exemple :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    &amp;lt;delete-button action=&quot;remove(user)&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
et&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope: {
        action: &#39;&amp;amp;&#39;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Ici AngularJS fournit comme valeur de la propriété &lt;i&gt;action&lt;/i&gt;&amp;nbsp;du scope isolé une fonction a exécuter pour déclencher l&#39;action, c&#39;est-à-dire l&#39;évaluation de l&#39;expression contenue dans l&#39;attribut &lt;i&gt;action&lt;/i&gt;. Il n&#39;y a pas de binding dans ce cas, c&#39;est juste une fonction à exécuter pour déclencher l&#39;action.&lt;br /&gt;
&lt;br /&gt;
On peut même passer à cette fonction un objet contexte dont les données vont surcharger celles du scope parent. Supposons qu&#39;on exécute ceci dans la directive :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope.action({user: previousUser});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Le &lt;i&gt;user&lt;/i&gt;&amp;nbsp;sera alors celui de l&#39;objet passé en paramètre, il ne sera pas pris dans le scope parent. Par contre la fonction &lt;i&gt;remove()&lt;/i&gt;&amp;nbsp;viendra elle du scope parent, car elle n&#39;est pas fournie dans l&#39;objet contexte passé en paramètre de l&#39;action.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Et sans scope isolé ?&lt;/h2&gt;
&lt;br /&gt;
Ces trois différents usages possibles des attributs au moyen de propriétés définies dans le scope isolé sont certes très pratiques, et permettent de simplifier le code de la directive. Mais on a vu avant qu&#39;on ne peut pas utiliser un scope isolé dans tous les cas, loin s&#39;en faut.&lt;br /&gt;
&lt;br /&gt;
Alors comment faire dans la fonction &lt;i&gt;link&lt;/i&gt;&amp;nbsp;de la directive&amp;nbsp;quand on doit se passer du côté pratique du scope isolé ? Et bien ce n&#39;est pas beaucoup plus compliqué.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    link: function (scope, element, attrs, ctrl) {
        // ...
    }&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Pour un&lt;b&gt;&amp;nbsp;attribut texte&lt;/b&gt;, on utilise la méthode&amp;nbsp;&lt;i&gt;$observe()&lt;/i&gt;&amp;nbsp;de l&#39;objet &lt;i&gt;attrs&lt;/i&gt;&amp;nbsp;contenant les attributs de l&#39;élément, avec le nom de l&#39;attribut à observer :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    attrs.$observe(&#39;xxx&#39;, function(value) {    // xxx est le nom de l&#39;attribut
        // ...
    });&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Pour une &lt;b&gt;expression valeur&lt;/b&gt;, on utilise la méthode &lt;i&gt;$watch()&lt;/i&gt; du scope, avec comme expression à surveiller la valeur de l&#39;attribut fournie par la propriété correspondante (en camel case) de l&#39;objet &lt;i&gt;attrs&lt;/i&gt;&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope.$watch(attrs.xxx, function(newVal, oldVal) {
        // ...
    });&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Et en sens inverse, pour mettre à jour la valeur de l&#39;expression contenue dans l&#39;attribut, s&#39;il s&#39;agit d&#39;une expression assignable bien sûr, on utilise le service &lt;i&gt;$parse&lt;/i&gt;, et la méthode &lt;i&gt;assign()&lt;/i&gt;&amp;nbsp;de l&#39;objet qu&#39;il renvoie :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    $parse(attrs.xxx).assign(scope, value);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Pour une &lt;b&gt;expression action&lt;/b&gt;, on utilise encore le service &lt;i&gt;$parse&lt;/i&gt;, et on appelle la fonction renvoyée par &lt;i&gt;$parse&lt;/i&gt;, en lui fournissant le scope en premier paramètre, et éventuellement en second paramètre un objet contexte qui surcharge certaines données du scope.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    $parse(attrs.xxx)(scope, locals);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Même si c&#39;est un peu plus complexe que de manipuler directement les propriétés du scope isolé, ça reste très raisonnable comme quantité de code à écrire.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Exemple de directive avec un scope isolé&lt;/h2&gt;
&lt;br /&gt;
Vous pouvez retrouver l&#39;exemple de la directive Google Maps que j&#39;ai montré lors de cette présentation &lt;a href=&quot;http://plnkr.co/edit/Mogdey?p=preview&quot;&gt;ici sur Plunker&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le code de la directive, on manipule uniquement les propriétés du scope isolé, que ce soit pour placer des watches afin d&#39;impacter la carte affichée quand les données changent dans le scope (parent), ou pour mettre à jour les données du scope d&#39;après les événements de la carte Google Maps.&lt;br /&gt;
&lt;br /&gt;
Voilà, avec tout ça vous avez de quoi jouer, avec ou sans scope isolé.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/5966303701062572993/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/10/scope-isole-dans-les-directives.html#comment-form' title='6 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/5966303701062572993'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/5966303701062572993'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/10/scope-isole-dans-les-directives.html' title='Scope isolé dans les directives AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSKe5L5-omRD_87IXbLRHvT_ZAnuhV8heluffgMrL8qsDOVMBOCVBz7tEFPDcgvCzAj_BFyH7oJnQC1socDgtUf4TRUP5X3pQMCyfWQ6c4ho6oianL-O62Oaf2UEf93LmsYT99UvQ7uByk/s72-c/scopes.png" height="72" width="72"/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-2296861489236136944</id><published>2013-10-26T16:49:00.001+02:00</published><updated>2013-10-26T16:49:50.483+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="service"/><category scheme="http://www.blogger.com/atom/ns#" term="test"/><category scheme="http://www.blogger.com/atom/ns#" term="évènement"/><title type='text'>Services ou événements dans l&#39;optique des tests</title><content type='html'>Quelques réflexions complémentaires à &lt;a href=&quot;http://www.frangular.com/2013/10/usage-des-services-angularjs.html&quot;&gt;mon article sur l&#39;usage des services&lt;/a&gt;, qui partent d&#39;une question que l&#39;on m&#39;a posé à la fin de ma présentation au Meetup AngularJS Paris chez Google. Quelqu&#39;un m&#39;a demandé si les événements pouvaient être utilisés pour simplifier les tests unitaires d&#39;un contrôleur. En limitant les injections de services en dépendances, on évite la nécessité de versions factices (&lt;i&gt;mock objects&lt;/i&gt;) de ces services.&lt;br /&gt;
&lt;br /&gt;
Cette question me turlupinait dans le trajet retour, et j&#39;ai essayé de pousser plus loin la réflexion en explorant cette piste et diverses alternatives.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Trop de services dans un contrôleur&lt;/h2&gt;
&lt;br /&gt;
En faisant un service pour chaque fonctionnalité, voire parfois plusieurs services pour une même fonctionnalité, ne risque-t-on pas de devoir injecter beaucoup trop de services à un même contrôleur ? Partons du principe qu&#39;on a correctement isolé tout le modèle de données et les traitements métier dans des services, et que le contrôleur se contente de publier dans le scope ce qui est utilisé dans les expressions des templates. Il ne doit y avoir aucun traitement complexe dans les contrôleurs, qui devraient donc être faciles à tester.&lt;br /&gt;
&lt;br /&gt;
Si l&#39;on a un contrôleur auquel on injecte de nombreux services, c&#39;est clairement le signe d&#39;un problème, et pas seulement pour tester ce contrôleur. On est typiquement dans le cas de l&#39;anti-pattern&amp;nbsp;&lt;i&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/God_object&quot;&gt;“God object”&lt;/a&gt;&lt;/i&gt;, l&#39;objet qui en fait beaucoup trop. Evidemment il va être complexe à tester, mais il est surtout trop complexe tout court. Le fait qu&#39;il soit difficile à tester n&#39;est qu&#39;un révélateur, pas le cœur du problème.&lt;br /&gt;
&lt;br /&gt;
Que faire avec un tel contrôleur ? On peut déjà sans doute le découper en plusieurs contrôleurs. S&#39;il s&#39;agit d&#39;un contrôleur associé à une des vues de l&#39;application (le template associé à une route), une vue certainement assez complexe, il est quand même probable que certains des services injectés ne sont utilisés que dans une portion de la vue. On peut alors associé un contrôleur séparé à cette portion de la vue, avec une directive ngController sur l&#39;élément HTML, ce qui crée un scope enfant. Ce nouveau contrôleur n&#39;aura besoin que d&#39;un seul service, ou d&#39;un tout petit nombre de services, pour l&#39;initialisation de ce scope enfant, qui hérite aussi de tout ce qui est publié dans son scope parent.&lt;br /&gt;
&lt;br /&gt;
Il ne faut pas hésiter à créer plusieurs contrôleurs à l&#39;intérieur d&#39;une vue, le code sera bien plus lisible avec plusieurs petits contrôleurs qu&#39;avec un seul contrôleur obèse, et chacun sera plus facile à tester, parce qu&#39;on aura réduit le nombre de dépendances.&lt;br /&gt;
&lt;br /&gt;
On peut aussi sans doute limiter le nombre de services à injecter en faisant des services de plus haut niveau. Les fonctionnalités un peu complexe de l&#39;application méritent d&#39;être découpées en plusieurs services, mais il s&#39;agit de découper l&#39;implémentation, pas l&#39;interface. Si ça revient à devoir injecter plein de petits services, ça ne va pas. Il faut faire un service de haut niveau en frontal, à injecter dans les contrôleurs et autre services qui l&#39;utilisent, et dont l&#39;implémentation est découpée en plusieurs services plus élémentaires. Mais les services élémentaires ne doivent pas être manipulés ni même connus de l&#39;extérieur, ils doivent être encapsulés dans le service de plus haut niveau. Donc le fait d&#39;avoir des services du niveau d&#39;abstraction que manipule le contrôleur doit permettre d&#39;éviter cette multiplicité des injections.&lt;br /&gt;
&lt;br /&gt;
C&#39;est par ces deux méthodes, en séparant des parties du code dans des petits contrôleurs liés à des portions de la vue et en créant des services de plus haut niveau, qu&#39;on va pouvoir régler le problème à la source, plutôt que d&#39;essayer de trouver comment tester plus facilement un contrôleur mal écrit.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Communication entre services ou par événements ?&lt;/h2&gt;
&lt;br /&gt;
Vaut-il mieux faire communiquer plutôt les services entre eux, ou passer par les événements d&#39;AngularJS pour découpler d&#39;avantage.&lt;br /&gt;
&lt;br /&gt;
Le fait de découpler est généralement plutôt une bonne chose, mais même des bonnes choses il ne faut pas abuser. Les événements d&#39;AngularJS ont trois caractéristiques très importantes :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;ils s&#39;appliquent sur les scopes dans la vue, ce qui implique qu&#39;il ne peut pas y avoir d&#39;événement entre les services, en l&#39;absence de scope&lt;/li&gt;
&lt;li&gt;la propagation se base sur l&#39;arborescence des scopes, vers la racine ou dans l&#39;autre sens&lt;/li&gt;
&lt;li&gt;il peut y avoir plusieurs listeners d&#39;un même événement, ou aucun&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Ça veut dire qu&#39;utiliser un événement n&#39;a du sens que s&#39;il faut prévenir de quelque chose un nombre inconnu de listeners attachés aux scope parents ou enfants suivant le sens de propagation choisi, donc en quelque sorte aux éléments HTML parents ou enfants. Ce n&#39;est pas du tout le même paradigme qu&#39;un appel de méthode sur un objet (un service injecté).&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Utiliser un événement pour éviter d&#39;appeler directement une méthode d&#39;un service, et surtout éviter d&#39;avoir à l&#39;injecter, c&#39;est détourner le concept pour en faire une utilisation illogique. Ce n&#39;est certainement pas la meilleure façon de rendre le code lisible et facile à comprendre.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Ça revient à compliquer le code, en détournant le concept d&#39;événement, en devant enregistrer un listener et émettre l&#39;événement, pour simplement espérer simplifier un peu les tests. Combattre le mal par le mal, ce n&#39;est pas une bonne idée, au moins en informatique, donc inutile d&#39;essayer de corriger un problème par l&#39;ajout d&#39;un autre problème.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Clairement, il faut limiter l&#39;usage des événements du scope aux rares cas où les trois caractéristiques citées plus haut s&#39;appliquent pleinement, et ne surtout pas essayer d&#39;en faire l&#39;outil générique qu&#39;ils ne sont pas.&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Les événements simplifient-ils vraiment les tests ?&lt;/h2&gt;
&lt;br /&gt;
Imaginons quand même qu&#39;on veuille utiliser des événements du scope dans l&#39;objectif des tests unitaires. Les rendent-ils vraiment plus simple ?&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Avec un couplage par événement, il n&#39;y a aucun paramètre à injecter. Pour les tests qui ne sont pas liés à l&#39;événement, il n&#39;y a donc rien de particulier à faire, pas d&#39;objet à substituer. Mais bien sûr, il faut aussi tester que l&#39;événement est bien déclenché lorsqu&#39;il doit l&#39;être et avec les bonnes données. Deux solutions sont possibles :&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;utiliser un faux scope, pour vérifier l&#39;appel de $emit() ou $broadcast()&lt;/li&gt;
&lt;li&gt;utiliser une mini hiérarchie de scopes avec un listener, pour vérifier qu&#39;il reçoit bien le bon événement&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
La première est la plus simple : un même&amp;nbsp;&lt;i&gt;mock object&lt;/i&gt;&amp;nbsp;du scope pourra faire les vérifications concernant les émissions d&#39;événements pour tous les tests des contrôleurs.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Et avec un service injecté comme dépendance du contrôleur ? Il faut lui substituer un faux service, pour tester seulement le code du contrôleur et non celui du service. Pour les tests qui ne concernent pas ce service, on peut carrément le remplacer par &lt;i&gt;null&lt;/i&gt;. Mais pour ceux qui entraînent l&#39;utilisation du service, il faut le remplacer par un objet factice spécifique à ce service, et vérifier les appels de méthodes qui lui sont faits.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
On peut créer cet objet factice du&amp;nbsp;service en utilisant un framework de doublure. Il en existe plusieurs en JavaScript, donc un qui est inclus dans Jasmine. Celui-ci peut créer des objets factices, mais aussi remplacer une méthode réelle d&#39;un objet réel par une implémentation factice. Il suffit d&#39;une seule ligne pour obtenir un mock objet avec les méthodes souhaitées.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Donc en fin de compte, si l&#39;on utilise un framework de doublure, le test d&#39;un contrôleur avec injection de service sera aussi simple à écrire que celui d&#39;un contrôleur avec événement.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Evénements entre les services&lt;/h2&gt;
&lt;br /&gt;
&lt;br /&gt;
En isolant chaque fonctionnalité de l&#39;application dans un service, éventuellement subdivisé en plusieurs services plus petits, on va créer pas mal de dépendances entre des services. Bien sûr là aussi il faut s&#39;inquiéter d&#39;un même service qui en prendrait beaucoup d&#39;autres en dépendance, un tel service mériterait sans doute un bon refactoring. Mais on va couramment avoir un service qui prend comme dépendances un ou deux autres services.
&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Dans une application plutôt conséquente, on peut vouloir limiter le couplage entre certains services, et créer un système d&#39;événements. Attention, il ne s&#39;agit évidemment pas d&#39;utiliser les événements des scopes pour faire communiquer deux services. Les services ne sont pas liés aux scopes. Mais on peut introduire de toutes pièces, sous la forme d&#39;un service bien sûr, un pattern &lt;i&gt;Observer&lt;/i&gt;&amp;nbsp;(ou&amp;nbsp;&lt;i&gt;Listener&lt;/i&gt;) utilisable entre les services de l&#39;application.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Dans ce cas il n&#39;y a évidemment aucune propagation, simplement un événement qui est envoyé par un unique service de gestion des événements aux listeners enregistrés. L&#39;intérêt est évidemment le découplage entre le déclencheur de l&#39;événement et un nombre quelconque d&#39;observateurs.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Il n&#39;existe pas de système d&#39;événements de ce type en standard dans AngularJS, mais il est très facile d&#39;en faire une implémentation basique bien suffisante, et d&#39;un usage plus large que les événements des scopes.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
On peut éventuellement lier ce système d&#39;événements à celui des scopes, en le mettant dans le $rootScope pour propager aux services inscrits, et qui ne sont pas nécessairement publiés dans le scope, des événements tels que les &lt;i&gt;$routeChange*&lt;/i&gt;.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Je me souviens d&#39;avoir vu passer un retour d&#39;expérience qui affirmait que sur une grosse application AngularJS, il était indispensable d&#39;avoir un véritable &lt;i&gt;Event Bus&lt;/i&gt;. Ou un autre pattern équivalent, mais je ne sais plus où j&#39;ai lu ça. Je ne serais peut-être pas aussi catégorique au point d&#39;en faire une obligation. Mais sur une grosse application, on a vraiment intérêt à faire du découpage, et à la diviser en plusieurs parties bien isolées. Et un &lt;i&gt;Event Bus&lt;/i&gt;&amp;nbsp;sert justement à découpler les différentes parties, en remplaçant une dépendance entre deux parties par une dépendance de chacune sur l&#39;&lt;i&gt;Event Bus&lt;/i&gt;.&amp;nbsp;On pourra signaler un changement de contexte important dans l&#39;application sans se préoccuper directement des impacts, car dans n&#39;importe quelle autre partie de l&#39;application les services concernés par ce changement se seront inscrits auprès de l&#39;&lt;i&gt;Event Bus&lt;/i&gt;&amp;nbsp;et en seront notifiés.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
En fait sur une grosse application, il est très probable qu&#39;un &lt;i&gt;Event Bus&lt;/i&gt;&amp;nbsp;entre les services s&#39;avère très utile, et si je ne le présente pas comme une obligation, c&#39;est juste parce que je préfère qu&#39;un pattern soit introduit quand on en a constaté le besoin, plutôt que sur des recommandations trop générales, ou parce que ça fait chic.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Et il ne faut surtout pas essayer de remplacer ainsi toutes les dépendances entre les services, et tous les appels de méthodes par des événements, ça n&#39;aurait aucun sens. Il s&#39;agit juste d&#39;introduire un petit nombre d&#39;événements, pour retirer quelques liens trop directs entre des parties autrement séparées de l&#39;application. L&#39;essentiel des appels de méthodes entre services doivent rester inchangés, sur des services injectés comme dépendances.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
***&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
Voilà pour ces quelques réflexions et digressions, et juste en résumé s&#39;il faut retenir 4 conseils :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;découpez vos contrôleurs, en extrayant des services et des sous-contrôleurs&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;n&#39;utilisez les événements des scopes que dans le cadre très restreint où ils peuvent être pertinents&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;dans les tests unitaires, utilisez un framework pour créer des mocks objets des services à injecter&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;créez un service &lt;i&gt;Event Bus&lt;/i&gt;&amp;nbsp;pour gérer des événements entre les services, si la taille ou la structure de votre application le justifie&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/2296861489236136944/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/10/services-ou-evenements-pour-tests.html#comment-form' title='1 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/2296861489236136944'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/2296861489236136944'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/10/services-ou-evenements-pour-tests.html' title='Services ou événements dans l&#39;optique des tests'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-4245469134122454316</id><published>2013-10-23T00:03:00.006+02:00</published><updated>2013-11-10T22:31:54.521+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="service"/><title type='text'>Usage des services AngularJS</title><content type='html'>Cet article est basé sur la
présentation que j&#39;ai faite hier au Meetup AngularJS Paris chez Google. &lt;a href=&quot;http://tchatel.github.io/slides-ngParis-octobre2013/&quot; target=&quot;_blank&quot;&gt;Les slides se trouvent ici&lt;/a&gt;, et &lt;a href=&quot;http://goo.gl/K2Stjk&quot;&gt;une vidéo a été mise en ligne là&lt;/a&gt;. Mais comme les slides perdent
beaucoup de leur intérêt sans les explications qui vont avec, ça
méritait bien un article - et même plusieurs, puisque j&#39;ai mis deux exemples dans des articles séparés (les liens sont vers la fin). Je vais aussi en écrire un sur l&#39;autre partie de ma présentation, sur les scopes isolés dans les directives.&lt;br /&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
S&#39;il y a une fonctionnalité que les
développeurs qui débutent sur AngularJS sous-estiment énormément,
c&#39;est bien les services. Je me rend compte régulièrement en
formation ou lors d&#39;audits que beaucoup de code pourrait être
largement simplifié en utilisant plus efficacement les services. Ça
n&#39;a d&#39;ailleurs rien d&#39;étonnant, on commence à partir des exemples
qu&#39;on trouve sur internet, qui sont des exemples courts et n&#39;ayant
évidemment pas toute la structure d&#39;une vraie application. Des
exemples qui montrent comment ça marche, pas comment il faut faire.&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
Dans une application non triviale, au
moins 90 % du code JavaScript devrait être dans les services. Il
peut y avoir quelques directives et filtres bien sûr, dont une
partie peut d&#39;ailleurs être commune à plusieurs applications, mais
ce sont les contrôleurs qui doivent être aussi réduits que
possible. Un contrôleur AngularJS est juste une fonction
d&#39;initialisation du scope correspondant, et il doit essentiellement
se limiter à cela : faire quelques affectations dans l&#39;objet scope.
Il ne doit pas faire de requêtes HTTP, ni des traitements sur les
données, ni gérer le stockage des données.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h2&gt;
Qu&#39;est-ce qu&#39;un service ?&lt;/h2&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
Un service n&#39;est rien d&#39;autre qu&#39;un singleton publié
sous un certain nom. On peut publier n&#39;importe quelle valeur comme
service, un objet JavaScript (y compris un tableau ou une fonction),
ou même une valeur primitive : chaîne de caractères, nombre,
booléen.&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
AngularJS gère l&#39;injection des
services là où ils sont indiqués comme dépendances, dans des
contrôleurs, dans d&#39;autres services, dans des directives ou des
filtres. Il s&#39;occupe aussi d&#39;instancier le service, si ce n&#39;est pas
un objet préexistant qui est publié. Une fois instancié et publié,
chaque injection du service fournira le même objet, par référence,
ou une copie de la même donnée primitive. C&#39;est pourquoi un service
permet de stocker des données qui seront conservées tant que
l&#39;application s&#39;exécute, et qui peuvent bien sûr être modifiées
en cours de route.&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
Pour les différentes façons de créer
un service, voir cet article antérieur :&lt;br /&gt;
-&amp;nbsp;&lt;a href=&quot;http://www.frangular.com/2012/12/differentes-facons-de-creer-un-service-angularjs.html&quot; target=&quot;&quot;&gt;Les différentes façons de créer un service avec AngularJS&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div style=&quot;margin-bottom: 0cm;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Que mettre en service ?&lt;/h2&gt;
&lt;br /&gt;
Pour faire simple, il faut écrire l&#39;essentiel du code d&#39;une
application sous la forme de services. Si l&#39;on excepte les directives
et les filtres, et qu&#39;on limite les contrôleurs au strict minimum
qui est de publier dans le scope les données et fonctions utilisées
dans les templates, tout le reste va dans les services.&lt;br /&gt;
&lt;br /&gt;
La liste qui suit est donc loin d&#39;être exhaustive.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
&lt;b&gt;Tout le code métier&lt;/b&gt;&lt;/h3&gt;
En particulier, tout le code métier, sans exception, doit être
organisé en services cohérents qui correspondent aux différentes
fonctionnalités de l&#39;application. C&#39;est la seule façon de s&#39;y
retrouver quand l&#39;application prend de l&#39;ampleur.&lt;br /&gt;
&lt;br /&gt;
Si l&#39;on peut
concevoir qu&#39;on prenne des raccourcis dans les exemples, et qu&#39;on
puisse avoir des règles métiers dans les contrôleurs et les vues,
c&#39;est totalement exclus dans une vraie application sur laquelle
différents développeurs interviennent, et ce sur une durée plus ou
moins longue. Comment s&#39;y retrouver si chaque fonctionnalité de
l&#39;application est éparpillée dans de multiples contrôleurs et
templates ? Même si AngularJS est un framework qui requiert peu de
code, si on ne l&#39;organise pas correctement il sera vite ingérable.&lt;br /&gt;
&lt;br /&gt;
Ça veut dire que pour chaque fonctionnalité de l&#39;application, il
faut créer un service qui en regroupe tout les aspects, qui gère
les données et fournit toutes les méthodes utiles. Attention en
particulier aux règles métier qui peuvent facilement se dissimuler
dans les conditions des templates. 
&lt;br /&gt;
&lt;br /&gt;
Par exemple si l&#39;on crée une application qui gère un compte
bancaire, on peut vouloir ajouter une classe CSS &lt;i&gt;&#39;warning&#39;&lt;/i&gt; pour
afficher en rouge le solde négatif d&#39;un compte. Si on met des
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;ng-class=&quot;{warning: compte.solde &amp;lt; 0}&quot;&lt;/span&gt;, ça n&#39;a l&#39;air de
rien, mais c&#39;est une règle métier qu&#39;on a glissé dans les
templates, sans doute à plusieurs endroits. Et le jour où la règle
métier change, parce qu&#39;il faut maintenant mettre la classe
&lt;i&gt;&#39;warning&#39;&lt;/i&gt; lorsque le solde est en dessous du découvert autorisé, il
va falloir revoir tous les templates, au risque d&#39;en oublier et
d&#39;avoir des incohérences dans l&#39;application. Alors que si l&#39;on a
fait ça proprement, en mettant la règle métier dans une fonction
&lt;i&gt;hasWarning()&lt;/i&gt; du service &lt;i&gt;&#39;compte&#39;&lt;/i&gt;, on n&#39;a qu&#39;un seul endroit à
changer pour la modifier.&lt;br /&gt;
&lt;br /&gt;
A l&#39;exception de certaines conditions purement techniques, comme
le fait de tester qu&#39;une propriété est définie, toutes les autres
se rapportent plus ou moins à des règles métier, et doivent aller
dans les services. Avantage supplémentaire, le fait de les mettre
sous la forme de fonctions dans les services les rend testables,
contrairement aux expressions en dur dans les templates.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Le code de présentation&lt;/h3&gt;
Les services ne se limitent pas au code métier de l&#39;application.
Il y a aussi toute une partie du code, qui est plutôt du code de
présentation général sans lien direct avec le métier, qui mérite
aussi d&#39;être organisé en services.&lt;br /&gt;
&lt;br /&gt;
J&#39;indique plus loin deux exemples (dans des articles séparés) qui sont directement des
fonctionnalités liées à la présentation : la conservation des
valeurs des critères d&#39;une page de recherche, et les notifications à
l&#39;utilisateur.&lt;br /&gt;
&lt;br /&gt;
Ce sont juste deux exemples de fonctionnalités qui doivent être
factorisées dans des services, même si on ne les utilise qu&#39;à un
seul endroit, car le fait de créer un service permet d&#39;isoler la
fonctionnalité, et de la tester plus facilement et indépendamment
de tout contrôleur. Et ça améliore nettement la lisibilité du
code de l&#39;application. Ce n&#39;est pas quelque chose de spécifique à
AngularJS, c&#39;est tout simplement la technique de refactoring &lt;i&gt;&quot;Extract
Class&quot;&lt;/i&gt;, enfin plutôt &quot;Extract Object&quot; en JavaScript :
le fait d&#39;extraire un objet du code du contrôleur permet de lui
donner un nom significatif (le nom du service), de nommer ses
méthodes, et de l&#39;isoler du reste du code. Les bonnes pratiques de
la programmation objet ont toujours cours dans une application
AngularJS.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Les requêtes&lt;/h3&gt;
Les requêtes HTTP à des web services ne sont qu&#39;un exemple de
code métier qui n&#39;a rien à faire dans les contrôleurs.&lt;br /&gt;
&lt;br /&gt;
Bien sûr dans les petits exemples on en voit partout, parce que
c&#39;est simple et que ça permet de montrer uniquement le
fonctionnement des requêtes sans devoir expliquer tout le reste. Je
fais aussi de tels exemples. Mais il faut les prendre pour ce qu&#39;ils
sont, des démonstrations techniques et surtout pas des exemples à
suivre de la façon d&#39;architecturer une vraie application.&lt;br /&gt;
&lt;br /&gt;
Les requêtes à des serveurs posent un problème particulier
quand on veut les mettre dans un service, c&#39;est la gestion de
l&#39;asynchronisme. Comment écrire un service fournissant des données,
mais qui doit faire des requêtes pour les obtenir, et donc qui n&#39;a
pas encore les données au moment où il devrait les renvoyer ?&lt;br /&gt;
&lt;br /&gt;
Le service ne peut évidemment pas renvoyer des données qu&#39;il n&#39;a
pas encore. Il n&#39;est pas possible non plus d&#39;attendre l&#39;arrivée des données
dans la fonction du service qui est appelée, car l&#39;interpréteur
JavaScript du navigateur est mono-thread, et la possibilité
d&#39;attendre n&#39;existe pas. La fonction doit renvoyer quelque chose
immédiatement. Trois solutions sont couramment utilisées :&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;utiliser une fonction callback : la méthode du service ne
 renvoie pas de valeur, mais prend en paramètre une fonction
 callback qui sera appelée plus tard en lui passant les données reçues&lt;br /&gt;

 &lt;/li&gt;
&lt;li&gt;renvoyer un objet ou un tableau vide, qui sera alimenté plus
 tard ; c&#39;est ce que fait $resource&lt;br /&gt;

 &lt;/li&gt;
&lt;li&gt;renvoyer une promise, qui sera résolue avec les données
 quand elles seront reçues du serveur&lt;br /&gt;

&lt;/li&gt;
&lt;/ul&gt;
Donc il est tout-à-fait possible de mettre les requêtes HTTP
dans des services, en utilisant l&#39;une ou l&#39;autre de ces solutions,
avec une préférence pour la troisième car l&#39;API de promises est
puissante et simplifie beaucoup la gestion de l&#39;asynchronisme.&lt;br /&gt;
&lt;br /&gt;
Petite remarque au passage sur les promises, depuis la toute
récente version 1.2.0-rc3 les promises ne peuvent plus être
utilisées directement dans les expressions AngularJS. Jusque là
l&#39;interpréteur d&#39;expressions remplaçait chaque promise par sa
valeur de résolution une fois qu&#39;elle était résolue, ce qui
permettait d&#39;utiliser une promise de résultat différé comme le
résultat lui-même. Cette possibilité est maintenant &lt;i&gt;deprecated&lt;/i&gt;,
et a même été désactivée par défaut.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Le modèle de données&lt;/h3&gt;
Un contrôleur AngularJS publie dans son scope les données
nécessaires à l&#39;évaluation des expressions du template.&lt;br /&gt;
&lt;br /&gt;
Ca ne veut pas dire que le modèle d&#39;une application AngularJS
doit résider dans les scopes, bien au contraire. Il doit être géré,
et conservé quand c&#39;est nécessaire, dans des services.&lt;br /&gt;
&lt;br /&gt;
Certains de ces services seront &lt;i&gt;stateless&lt;/i&gt;,
et d&#39;autres &lt;i&gt;statefull&lt;/i&gt;
s&#39;il s&#39;agit d&#39;une entité du modèle qui doit être conservée de
façon globale, comme par exemple l&#39;utilisateur connecté. Au
contraire, si une vue de l&#39;application affiche dans un formulaire une
instance d&#39;une collection d&#39;entités fournies par un serveur REST par
exemple, dans ce cas le service va être stateless, et simplement
renvoyer pour qu&#39;elle soit publiée dans le scope l&#39;entité
correspondant à la vue courante.&lt;br /&gt;
&lt;br /&gt;
Ça dépend si l&#39;on veut que
l&#39;instance soit conservée de façon globale et qu&#39;on la retrouve
dans le même état quand on revient sur la vue, ou s&#39;il faut que
cette vue charge une instance précise dont l&#39;identifiant est passé
dans l&#39;URL de la route.&lt;br /&gt;
&lt;br /&gt;
Mais dans tous les cas, qu&#39;il
conserve ou non l&#39;instance courante, le service contient tout le code
des traitements qui s&#39;y appliquent, soit directement comme des
méthodes du service, soit comme des méthodes ajoutées à
l&#39;instance.&lt;br /&gt;
&lt;br /&gt;
Le fait que le modèle soit géré dans les services permet aussi
d&#39;injecter ces services dans d&#39;autres services, et donc de bien
organiser le découpage de son modèle de données (incluant les
traitements) sous la forme d&#39;un ensemble de services correctement
isolés et qui coopèrent, chose totalement impossible si les données
se trouvent uniquement dans les scopes.&lt;br /&gt;
&lt;br /&gt;
Différents patterns sont utilisables pour créer les services qui
travaillent sur des entités persistantes, comme par exemple le
pattern Active Record popularisé par Ruby on Rails, le pattern
Repository du DDD (Domain Driven Design), ou encore pour des services
statefull le pattern Entity Home abondamment utilisé dans le
framework Seam.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Découper les services&lt;/h2&gt;
&lt;br /&gt;
Après avoir dit de rassembler tous les constituants de chaque
fonctionnalité de l&#39;application dans un même service, me voici à
recommander de les découper.&lt;br /&gt;
&lt;br /&gt;
Comme de façon générale en programmation objet, il vaut mieux
éviter d&#39;avoir des services obèses. Si une fonctionnalité peut
être découpée, parce qu&#39;elle recouvre plusieurs aspects distincts,
ou qu&#39;elle concerne plusieurs couches différentes comme la
communication avec le serveur, les traitements métier et la
présentation, alors il vaut mieux faire plusieurs services qu&#39;un
seul. Le code n&#39;en sera que plus compréhensible, et ce n&#39;est pas
contradictoire avec le fait de rassembler les éléments d&#39;une même
fonctionnalité si l&#39;on ne mélange pas plusieurs fonctionnalités
entre elles.&lt;br /&gt;
&lt;br /&gt;
Il faut prendre exemple sur AngularJS lui-même, car le framework
est conçu comme un assemblage de petits services, avec des services
de haut niveau et d&#39;autres de bas niveau.&lt;br /&gt;
&lt;br /&gt;
Pour les requêtes HTTP, le service &lt;i&gt;&lt;b&gt;$http&lt;/b&gt;&lt;/i&gt; s&#39;appuie sur un service
&lt;i&gt;&lt;b&gt;$httpBackend&lt;/b&gt;&lt;/i&gt; qui réalise l&#39;envoi de la requête et la réception de
la réponse. Ce découpage permet de remplacer ce service de bas
niveau &lt;i&gt;$httpBackend&lt;/i&gt; par une version &lt;i&gt;&#39;mock object&#39;&lt;/i&gt; fournie avec
AngularJS, qui évite de faire réellement les requêtes dans les
tests unitaires. On a même le service de plus haut niveau &lt;i&gt;$resource&lt;/i&gt;
qui s&#39;appuie sur &lt;i&gt;$http&lt;/i&gt;, pour gérer des entités persistantes sur un
serveur REST.&lt;br /&gt;
&lt;br /&gt;
Pour la gestion du routage interne à l&#39;application, le service
&lt;i&gt;&lt;b&gt;$route&lt;/b&gt;&lt;/i&gt; s&#39;appuie sur le service bas niveau &lt;i&gt;&lt;b&gt;$location&lt;/b&gt;&lt;/i&gt; qui gère l&#39;URL.&lt;br /&gt;
&lt;br /&gt;
En faisant de même, en découpant nos propres services lorsqu&#39;ils
s&#39;y prêtent, on aura un code plus facile à comprendre, et aussi
plus facile à tester. Si une fonctionnalité est découpée en
plusieurs petits services, ce sera plus facile de tester chaque petit
service séparément, en remplaçant les autres par des
implémentations factices (mock objects). S&#39;il y a un seul service et
que certaines parties doivent être courcircuitées dans les tests
unitaires, il va falloir bricoler l&#39;unique objet au niveau de ses
méthodes, en espérant que les parties en questions sont au moins
dans des méthodes séparées, mais ça sera de toute façon beaucoup
moins pratique qu&#39;avec plusieurs services.&lt;br /&gt;
&lt;br /&gt;
Pour les services classiques permettant l&#39;accès à des entités
persistantes, et qui font aussi quelques traitements sur ces données,
notamment après leur réception du serveur ou avant leur envoi pour
sauvegarde, il y a une séparation naturelle entre les traitements
sur les entités et la communication avec le serveur, qui gagneront
donc être séparés en deux services distincts.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Préférer les services aux événements du scope&lt;/h2&gt;
&lt;br /&gt;
Je n&#39;ai jamais caché que je ne suis pas fan des événements qu&#39;on peut propager entre les scopes. Ce n&#39;est pas le concept en soi qui me gêne, mais
plus le fait qu&#39;il soit largement trop utilisé, et presque toujours
dans des conditions où il n&#39;est pas judicieux, et où un service
serait bien plus adapté.&lt;br /&gt;
&lt;br /&gt;
Bon évidemment, quand on a utilisé jQuery depuis des années, on
doit trouver qu&#39;AngularJS manque d&#39;événements, et du coup les
événements du scope doivent activer un réflexe pavlovien
révélateur d&#39;un conditionnement dont beaucoup de développeurs ont
du mal à s&#39;extraire. D&#39;ailleurs si je me méfie beaucoup des
événements du scope, j&#39;ai une aversion bien plus grande pour
l&#39;attribut &lt;b&gt;ng-change&lt;/b&gt; des
champs de formulaire, qui ressemble davantage à une approche jQuery
qu&#39;à celle d&#39;AngularJS. Mais j&#39;en reparlerai une prochaine fois.&lt;br /&gt;
&lt;br /&gt;
Les événements du scope, qui
se propagent dans un sens ou dans l&#39;autre de l&#39;arborescence des
scopes, n&#39;ont de sens que pour une communication liée à la
structure du HTML. Dans tous les autres cas, bien plus fréquents, il
est plus logique et plus simple d&#39;utiliser un service.&lt;br /&gt;
&lt;br /&gt;
La façon naturelle avec
AngularJS de partager des données entre deux contrôleurs... c&#39;est
de ne pas le faire. Car comme je l&#39;ai expliqué plus haut, les
données doivent être gérées dans les services, et si deux
contrôleurs utilisent le même service, ils travaillent
naturellement sur les mêmes données et les mêmes traitements, et
il n&#39;y a aucune raison pour qu&#39;ils essaient de communiquer autrement.&lt;br /&gt;
&lt;br /&gt;
De plus l&#39;héritage par
prototype entre les scopes fait qu&#39;un objet publié dans son scope
par un contrôleur associé à un élément HTML sera accessible aux
scopes et contrôleurs correspondant à des éléménts enfants.&lt;br /&gt;
&lt;br /&gt;
Du coup, s&#39;il existe des cas où
l&#39;utilisation des événements est pertinente, je ne le nie pas, ils sont
plutôt rares, et il vaut bien mieux avoir d&#39;abord le réflexe
service plutôt que le réflexe événements. Mais c&#39;est l&#39;inverse
que je constate, avec des utilisations complètements illogiques des
événements, allant jusqu&#39;à récupérer le $rootScope pour propager
un événement dans toute l&#39;arborescence des scopes parce que le
scope cible n&#39;est pas dans la bonne branche, ce qui est véritable
hérésie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Exemples d&#39;utilisation des services&lt;/h2&gt;
&lt;br /&gt;
Deux des exemples que j&#39;ai présentés hier méritent un article
séparé, car ils sont facilement réutilisables en dehors du cadre
de cette présentation des usages des services. Vous pouvez donc les trouver sur les pages suivantes :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.frangular.com/2013/10/conserver-valeurs-criteres-recherche.html&quot;&gt;Conserver les valeurs des critères de recherche&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.frangular.com/2013/10/service-angularjs-de-notification.html&quot;&gt;&lt;span id=&quot;goog_152515325&quot;&gt;&lt;/span&gt;Service AngularJS de notification&lt;/a&gt;&lt;span id=&quot;goog_152515326&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
J&#39;ai parlé aussi d&#39;un service gérant l&#39;utilisateur connecté, en
donnant des pistes pour son implémentation. Il est important de créer un service pour la gestion de l&#39;utilisateur, avec ses données et des fonctions par exemple pour vérifier ses droits, plutôt que de mettre ça plus ou moins en vrac dans le $rootScope.&lt;br /&gt;
&lt;br /&gt;
Attention, je n&#39;ai rien contre le fait de publier dans le $rootScope l&#39;utilisateur connecté. C&#39;est une donnée globale de l&#39;application, et si on l&#39;utilise à pas mal d&#39;endroits pour conditionner l&#39;affichage en fonction de ses droits, le plus simple est effectivement de l&#39;avoir dans le $rootScope, pour y accéder depuis n&#39;importe quel template. Mais il faut d&#39;abord en faire un service, et publier ce service (ou une partie de ce service) dans le $rootScope. Comme ça le service lui-même peut être injecté dans d&#39;autres services, ou dans une directive, sans avoir à aller chercher les données dans le scope - qui comme je l&#39;ai dit n&#39;est pas l&#39;endroit où stocker les données du modèle.&lt;br /&gt;
&lt;br /&gt;
Donc notre service pourra être un objet de ce style (et probablement moins simpliste) :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;{
    profile:  {
        firstname: &quot;Thierry&quot;,
        lastname: &quot;Chatel&quot;
    },
    hasRole: function (role) {
        return ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
On crée un contrôleur sur l&#39;élément &amp;lt;body&amp;gt;, qui publie le service complet dans le $rootScope. Si on a un service qui est un objet plus complexes, et donc seulement une partie concerne les templates, on peut faire un sous-objet avec cette partie, et publier seulement celle-ci dans le scope. N&#39;hésitez pas à être imaginatifs.&lt;br /&gt;
&lt;br /&gt;
Une remarque importante qui concerne la sécurité de l&#39;application : il ne faut jamais cacher côté client des données auxquelles l&#39;utilisateur ne doit pas avoir accès. C&#39;est bien trop facile à contourner. Les données non autorisées ne doivent pas être envoyées par le serveur. Donc quand je parle de conditionner l&#39;affichage en fonction des droits de l&#39;utilisateur, c&#39;est pour ne pas afficher une section ou un formulaire vide dans la page (les données n&#39;ayant pas été envoyées par le serveur), c&#39;est de l&#39;esthétique et pas de la sécurité.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
***&lt;/div&gt;
&lt;br /&gt;
Voilà pour ce long article, bravo à ceux qui ont eu le courage de le lire jusqu&#39;au bout, et amusez-vous bien avec les services !&lt;br /&gt;
&lt;br /&gt;
J&#39;ai écrit quelques réflexions complémentaires dans un nouvel article, qui peut compléter celui-ci :&lt;br /&gt;
- &lt;a href=&quot;http://www.frangular.com/2013/10/services-ou-evenements-pour-tests.html&quot;&gt;Services ou événements dans l&#39;optique des tests&lt;/a&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/4245469134122454316/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/10/usage-des-services-angularjs.html#comment-form' title='4 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4245469134122454316'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4245469134122454316'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/10/usage-des-services-angularjs.html' title='Usage des services AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-5911689961159909749</id><published>2013-10-23T00:02:00.000+02:00</published><updated>2013-10-23T00:06:58.440+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$scope"/><category scheme="http://www.blogger.com/atom/ns#" term="service"/><title type='text'>Conserver les valeurs des critères de recherche</title><content type='html'>Comment conserver lorsque l&#39;on change de vue à l&#39;intérieur d&#39;une application AngularJS des données correspondant à l&#39;état de la vue, comme les valeurs des critères d&#39;une recherche ?&lt;br /&gt;
&lt;br /&gt;
Imaginons qu&#39;on affiche dans une vue une liste d&#39;objets, avec des critères permettant de faire un filtrage en local. Et sur chaque objet, un lien permet de naviguer vers une vue des détails de l&#39;objet. Quand l&#39;utilisateur revient sur la liste, il s&#39;attend à la retrouver dans le même état. Ça suppose que les valeurs de filtrage saisies soient conservées.&lt;br /&gt;
&lt;br /&gt;
Or si ces valeurs sont simplement dans le scope de la vue, elles seront perdues à chaque changement de vue, car le scope de l&#39;ancienne vue est détruit par AngularJS. La solution simple pour les conserver est de les stocker dans un service.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
Vous pouvez voir &lt;a href=&quot;https://github.com/tchatel/angular-notifications&quot;&gt;un exemple sur GitHub, avec le lien vers la démo&lt;/a&gt;&amp;nbsp;(c&#39;est le même que pour l&#39;article &lt;i&gt;&lt;a href=&quot;http://www.frangular.com/2013/10/service-angularjs-de-notification.html&quot;&gt;&quot;Service AngularJS de notification&quot;&lt;/a&gt;)&lt;/i&gt;. Saisissez une valeur de filtre dans le catalogue, et changez de vue par exemple en cliquant sur l&#39;image d&#39;un des articles. Revenez ensuite au catalogue, par le lien du menu ou avec le bouton &lt;i&gt;&quot;Page précédente&quot; &lt;/i&gt;du navigateur. La valeur du filtre a été conservée.&lt;br /&gt;
&lt;br /&gt;
Il suffit pour cela de publier un service qui est un simple objet vide. Le code dans le fichier js/services.js se résume à cette ligne (appliquée à l&#39;objet module) :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;.value(&#39;search&#39;, {})&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Tout service AngularJS est un singleton, on est assuré de récupérer à chaque fois le même objet, ce qui permet d&#39;y conserver des données tant qu&#39;on reste sur l&#39;application.&lt;br /&gt;
&lt;br /&gt;
Ce service est injecté au contrôleur CatalogCtrl de la vue catalogue, et publié dans le scope associé (dans le fichier js/controllers.js) :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$scope.search = search;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Dans le template partials/catalog.html, le binding du champ de saisie du filtre se fait par la directive ngModel sur une propriété du service, qu&#39;on utilise aussi dans le filtre &lt;i&gt;&#39;filter&#39;&lt;/i&gt;&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;input ng-model=&quot;search.text&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
et&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;li ng-repeat=&quot;game in catalog | filter:search.text&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
C&#39;était simple, non ?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Et pour aller plus loin...&lt;/h2&gt;
&lt;br /&gt;
Bien sûr cette technique peut s&#39;utiliser pour conserver toutes sortes de données liées à l&#39;état d&#39;une vue, comme des options de visualisation, ou le nombre d&#39;éléments affichés simultanément dans le cas d&#39;une liste paginée, ou encore les colonnes affichées d&#39;une table si on permet à l&#39;utilisateur de les choisir.&lt;br /&gt;
&lt;br /&gt;
On peut créer un service un peu plus sophistiqué, avec une méthode qui renvoie l&#39;état conservé d&#39;une vue d&#39;après un identifiant quelconque attribué à la vue et passé en paramètre, et qui l&#39;initialise si nécessaire avec un état par défaut passé en second paramètre. Il n&#39;y a plus qu&#39;à utiliser cette méthode dans chaque contrôleur d&#39;une vue qui a des données à conserver, et publier l&#39;état dans le scope.&lt;br /&gt;
&lt;br /&gt;
On aura une utilisation de ce service, appelons-le &lt;i&gt;viewState&lt;/i&gt;, qui pourra ressembler à ça :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$scope.state = viewState.get(&#39;MainListView&#39;, {
    pageSize: 50,
    showComments : false
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Et ce service &lt;i&gt;viewState&lt;/i&gt;&amp;nbsp;n&#39;est pas compliqué à écrire, il suffit d&#39;appeler sur le module :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;.factory(&#39;viewState&#39;, function () {
    var states = {};
    return {
        get: function (id, defaultState) {
            return states[id] || (states[id] = defaultState);
        }
    };
})&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/5911689961159909749/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/10/conserver-valeurs-criteres-recherche.html#comment-form' title='1 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/5911689961159909749'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/5911689961159909749'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/10/conserver-valeurs-criteres-recherche.html' title='Conserver les valeurs des critères de recherche'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-4549270280307092435</id><published>2013-10-23T00:01:00.000+02:00</published><updated>2013-10-24T19:20:33.042+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="service"/><title type='text'>Service AngularJS de notification</title><content type='html'>Un autre exemple de service qui peut être très utile : il s&#39;agit d&#39;afficher des notifications pendant quelques secondes, comme le font les applications de Google, avec un lien dans le message permettant d&#39;annuler l&#39;action.&lt;br /&gt;
&lt;br /&gt;
C&#39;est typiquement le genre de truc qu&#39;on imagine compliqué, mais si on le prend bien, avec AngularJS ça s&#39;écrit en quelques lignes - enfin sans compter le CSS qui finalement est le plus gros du boulot.&lt;br /&gt;
&lt;br /&gt;
L&#39;exemple se trouve &lt;a href=&quot;https://github.com/tchatel/angular-notifications&quot;&gt;ici sur GitHub&lt;/a&gt;, et il y a un lien vers la démo (c&#39;est le même que pour l&#39;article &lt;i&gt;&lt;a href=&quot;http://www.frangular.com/2013/10/conserver-valeurs-criteres-recherche.html&quot;&gt;&quot;Conserver les valeurs des critères de recherche&quot;&lt;/a&gt;&lt;/i&gt;). Ajoutez des articles au panier, et quand vous en supprimez du panier, vous avez une notification avec un lien pour annuler l&#39;action.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
Cette fonctionnalité de notifications est dans deux fichiers : un template à inclure et un module à ajouter comme dépendance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Un template&lt;/h2&gt;
&lt;br /&gt;
Le template partials/notifications.html, à inclure dans la page index.html de l&#39;application, n&#39;a rien de compliqué : un &amp;lt;div&amp;gt; avec un contrôleur, un &amp;lt;ul&amp;gt; avec un &amp;lt;li&amp;gt; répété sur le contenu de la liste des notifications - il peut y en avoir plusieurs en même temps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;notifications&quot; ng-controller=&quot;NotificationsCtrl&quot;&amp;gt;
    &amp;lt;ul&amp;gt;
        &amp;lt;li ng-repeat=&quot;notif in notifications&quot;&amp;gt;
            &amp;lt;span ng-bind=&quot;notif.text&quot;&amp;gt;&amp;lt;/span&amp;gt;
            &amp;lt;a ng-if=&quot;notif.canUndo()&quot; 
                  ng-click=&quot;notif.undo()&quot; href=&quot;&quot;&amp;gt;Annuler&amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Chaque notification affiche son texte, et si elle le &lt;i&gt;undo&lt;/i&gt;&amp;nbsp;est possible, un lien &lt;i&gt;&quot;Annuler&quot;&lt;/i&gt;&amp;nbsp;qui appelle la fonction &lt;i&gt;undo()&lt;/i&gt;&amp;nbsp;quand on clique dessus.&lt;br /&gt;
&lt;br /&gt;
Le module se trouve lui dans le fichier js/notif.js. Il contient le contrôleur, qui se contente de publier la liste des notifications dans le scope :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;.controller(&#39;NotificationsCtrl&#39;, function ($scope, notification) {
    $scope.notifications = notification.list;
})&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Un service&lt;/h2&gt;
&lt;br /&gt;
Tout le code - pas si long que ça - est en fait dans le service &lt;i&gt;&lt;b&gt;notification&lt;/b&gt;&lt;/i&gt;, dans le même module :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;.factory(&#39;notification&#39;, function ($timeout) {
    var service = {
        list: {},
        add: function (text, undo, delay) {
            var timestamp = (new Date()).getTime();
            service.list[timestamp] = {
                text: text,
                canUndo: function () {
                    return angular.isFunction(undo);
                },
                undo: function () {
                    if (angular.isFunction(undo)) {
                        delete service.list[timestamp];
                        undo();
                    }
                }
            };
            $timeout(function () {
                delete service.list[timestamp];
            }, (delay || 5) * 1000);
        }
    };
    return service;
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Le service conserve une liste des notifications, nommée avec beaucoup d&#39;originalité &lt;i&gt;list&lt;/i&gt;.&lt;br /&gt;
Et une fonction d&#39;ajout d&#39;une notification, qui prend en paramètres le texte de la notification, la fonction d&#39;annulation, et une durée d&#39;affichage en seconde.&lt;br /&gt;
&lt;br /&gt;
Cette fonction d&#39;ajout utilise un timestamp en millisecondes comme identifiant de la notification - c&#39;était suffisant pour la démo, où il faudrait que l&#39;utilisateur soit particulièrement rapide pour qu&#39;il arrive à faire plusieurs opérations pendant la même milliseconde. Mais pour que ce service soit plus général, et que des notifications puissent éventuellement être créées par d&#39;autres services sans risque de collision, il faudrait utiliser un identifiant plus unique que ça.&lt;br /&gt;
&lt;br /&gt;
Donc la fonction ajoute à la liste un objet contenant le texte de la notification, une fonction &lt;i&gt;canUndo()&lt;/i&gt;&amp;nbsp;qui indique si l&#39;on a fourni une fonction d&#39;annulation, et une fonction &lt;i&gt;undo()&lt;/i&gt;&amp;nbsp;à appeler pour annuler l&#39;action et qui retire aussi la notification de la liste pour qu&#39;elle disparaisse après annulation.&lt;br /&gt;
&lt;br /&gt;
Après l&#39;ajout dans la liste, elle programme un &lt;i&gt;$timeout &lt;/i&gt;pour retirer la notification au bout de la durée d&#39;affichage indiquée, ou 5 secondes par défaut.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Utilisation du service&lt;/h2&gt;
&lt;br /&gt;
Alors comment s&#39;utilise ce service ? C&#39;est dans le fichier js/controllers.js que ça se passe. La fonction de suppression d&#39;un article du panier est celle-ci :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;        remove: function (row) {
            var self = this;
            delete self.rows[row.game.ref];
            notification.add(&quot;Article supprimé : &quot; + row.game.name + &quot;. &quot;, 
                             function () {
                self.rows[row.game.ref] = row;
            }, 6);
        }&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Après le &lt;i&gt;delete&lt;/i&gt;&amp;nbsp;qui supprime la ligne du panier de l&#39;objet indexé &lt;i&gt;rows&lt;/i&gt;, elle appelle l&#39;ajout d&#39;une notification, en passant le message, une fonction d&#39;annulation qui se contente de remettre la ligne supprimée à sa place antérieure, et une durée.&lt;br /&gt;
&lt;br /&gt;
Et le tour est joué !&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/4549270280307092435/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/10/service-angularjs-de-notification.html#comment-form' title='6 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4549270280307092435'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4549270280307092435'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/10/service-angularjs-de-notification.html' title='Service AngularJS de notification'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-779396707887870616</id><published>2013-10-14T18:05:00.000+02:00</published><updated>2013-10-14T18:07:34.395+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="formulaire"/><category scheme="http://www.blogger.com/atom/ns#" term="validation"/><title type='text'>Validateur pour vérifier qu&#39;une date est postérieure à une autre</title><content type='html'>Voici un exemple de validateur spécifique, qui répond au besoin classique de vérifier que la date saisie dans un champ est postérieure à celle saisie dans un autre champ du formulaire. Typiquement, on demande de saisir une date de début et une date de fin, et on veut s&#39;assurer qu&#39;un utilisateur étourdi n&#39;a pas rentré une date de fin qui précède la date de début.&lt;br /&gt;
&lt;br /&gt;
L&#39;exemple est intéressant, car il permet de voir comment valider un champ par rapport à un autre, ce qui n&#39;est pas expliqué dans la documentation d&#39;AngularJS.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h2&gt;
Pourquoi un validateur spécifique ?&lt;/h2&gt;
&lt;br /&gt;
Evidemment, on peut toujours conditionner l&#39;affichage d&#39;un message d&#39;erreur sur une simple condition portant sur les propriétés du scope, par exemple :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;endDate &amp;gt;= startDate&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
C&#39;est tout simple, mais ça ne s&#39;intègre pas à la validation des formulaires d&#39;AngularJS. Le message d&#39;erreur va s&#39;afficher correctement, mais le formulaire ne sera pas considéré comme invalide.&lt;br /&gt;
&lt;br /&gt;
Ça veut dire qu&#39;il faut penser à ajouter aussi cette condition là où l&#39;on se base sur la validité du formulaire, par exemple pour désactiver le bouton de soumission. Le fait d&#39;ajouter une condition comme celle-ci de façon externe au mécanisme de validation du framework va conduire à devoir l&#39;ajouter à plusieurs endroits, avec le risque d&#39;oublier, et donc d&#39;avoir des incohérences. Surtout si c&#39;est un autre développeur qui ajoute plus tard une condition portant sur la validité du formulaire, il y a peu de chances pour qu&#39;il pense à ajouter cette condition entre les deux dates.&lt;br /&gt;
&lt;br /&gt;
C&#39;est donc une solution simple, mais qui a un impact plutôt négatif sur la robustesse du code. Heureusement, ce n&#39;est pas bien compliqué non plus de créer un vrai validateur qui s&#39;intègre au mécanisme du framework.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Une directive pour valider&lt;/h2&gt;
&lt;br /&gt;
En réalité il n&#39;y a pas en soi de validateurs dans AngularJS, ce qu&#39;on crée pour ajouter des conditions de validation spécifiques, ce sont tout simplement des directives, qui peuvent ajouter ou non un &lt;i&gt;parser&lt;/i&gt;&amp;nbsp;au champ de saisie.&lt;br /&gt;
&lt;br /&gt;
Donc ce qu&#39;on va créer, c&#39;est une directive &lt;i&gt;&lt;b&gt;dateAfter&lt;/b&gt;&lt;/i&gt;, à utiliser sur la date de fin, pour spécifier qu&#39;elle doit être postérieure à la date de début :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;input ng-model=&quot;data.toDate&quot; date-after=&quot;data.fromDate&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
La valeur de cet attribut &lt;b&gt;&lt;i&gt;date-after&lt;/i&gt;&lt;/b&gt;&amp;nbsp;placé sur la date de fin est une espression AngularJS, exactement la même que celle du &lt;i&gt;&lt;b&gt;ng-model&lt;/b&gt;&amp;nbsp;&lt;/i&gt;du champ de saisie de la date de début.&lt;br /&gt;
&lt;br /&gt;
Vous pouvez voir le code complet de l&#39;exemple &lt;a href=&quot;http://plnkr.co/edit/VWgZus?p=preview&quot; target=&quot;_blank&quot;&gt;ici sur Plunker&lt;/a&gt;, et faire des essais. L&#39;exemple utilise le date picker de &lt;a href=&quot;http://angular-ui.github.io/bootstrap/&quot; target=&quot;_blank&quot;&gt;UI Bootstrap&lt;/a&gt; pour la saisie de la date, lequel convertit la date saisie en un objet Date JavaScript dans le modèle, ce qui simplifie d&#39;ailleurs la comparaison.&lt;br /&gt;
&lt;br /&gt;
La directive elle-même n&#39;est pas bien compliquée :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;  .directive(&#39;dateAfter&#39;, function () {
    return {
      require: &#39;ngModel&#39;,
      link: function (scope, element, attrs, ngModelCtrl) {
        var date, otherDate;
        scope.$watch(attrs.dateAfter, function (value) {
          otherDate = value;
          validate();
        });
        scope.$watch(attrs.ngModel, function (value) {
          date = value;
          validate();
        });
        function validate() {
          ngModelCtrl.$setValidity(&#39;dateAfter&#39;,
                 !date || !otherDate || date &amp;gt;= otherDate);
        }        
      }
    };
  })&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Elle récupère bien sûr le &lt;a href=&quot;http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController&quot; target=&quot;_blank&quot;&gt;ngModelController&lt;/a&gt;&amp;nbsp;(contrôleur créé par la directive&amp;nbsp;&lt;b&gt;ngModel&lt;/b&gt;) de l&#39;élément courant,&amp;nbsp;en l&#39;occurrence le champ de saisie de la date de fin, pour pouvoir indiquer sa validité.&lt;br /&gt;
&lt;br /&gt;
Or la validité doit être revérifiée si l&#39;une ou l&#39;autre des dates change. Il suffit pour cela de mettre deux watches sur le scope :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;un qui porte sur l&#39;autre date, en surveillant l&#39;expression qui est dans l&#39;attribut &lt;i&gt;&lt;b&gt;date-after&lt;/b&gt;&lt;/i&gt; (récupérée via &lt;i&gt;attrs.dateAfter&lt;/i&gt;)&lt;/li&gt;
&lt;li&gt;et l&#39;autre qui porte sur la date du champ courant, le champ sur lequel est placée la directive, en surveillant l&#39;expression de l&#39;attribut &lt;i&gt;&lt;b&gt;ng-model&lt;/b&gt;&lt;/i&gt; (via &lt;i&gt;attrs.ngModel&lt;/i&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Ces deux watches stockent la nouvelle valeur de l&#39;expression qu&#39;ils surveillent dans une variable locale de la directive, et déclenchent la fonction de validation. Dans la fonction de validation, il suffit d&#39;utiliser la méthode &lt;i style=&quot;font-weight: bold;&quot;&gt;$setValidity()&lt;/i&gt;&amp;nbsp;du &lt;b&gt;ngModelController&lt;/b&gt;&amp;nbsp;pour positionner une clef de validation &lt;b&gt;&#39;dateAfter&#39;&lt;/b&gt;&amp;nbsp;avec un booléen indiquant si le champ est valide.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Pas de &lt;i&gt;parser&lt;/i&gt;&amp;nbsp;?&lt;/h2&gt;
&lt;br /&gt;
Les exemples de validateurs spécifiques qui sont dans la documentation d&#39;AngularJS (&lt;a href=&quot;http://docs.angularjs.org/guide/forms&quot; target=&quot;_blank&quot;&gt;Forms&lt;/a&gt;, § Custom Validation) ajoutent au &lt;b&gt;ngModelControler &lt;/b&gt;un&amp;nbsp;&lt;i&gt;parser &lt;/i&gt;qui est chargé de valider la valeur saisie.&lt;br /&gt;
&lt;br /&gt;
On pourrait faire la même chose pour la date du champ courant, mais dans ce cas il faudrait mettre le parser en fin de tableau, pour qu&#39;il s&#39;exécute après celui du date picker de UI Bootstrap.&lt;br /&gt;
&lt;br /&gt;
Mais je préfère utiliser le $watch, car de cette façon, ça marche même si on modifie la date de début via du code JavaScript dans le contrôleur. Tandis qu&#39;un&amp;nbsp;&lt;i&gt;parser&lt;/i&gt;&amp;nbsp;refait la validation seulement quand l&#39;utilisateur fait une saisie dans le champ, pas lorsque la propriété est modifiée par du code.&lt;br /&gt;
&lt;br /&gt;
Voilà pour cet exemple qui pourra être utile à tous ceux qui ont besoin de valider un champ par rapport à un autre.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/779396707887870616/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/10/validateur-date-posterieure.html#comment-form' title='1 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/779396707887870616'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/779396707887870616'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/10/validateur-date-posterieure.html' title='Validateur pour vérifier qu&#39;une date est postérieure à une autre'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-1502168598714424450</id><published>2013-05-28T16:03:00.002+02:00</published><updated>2013-05-28T16:03:52.333+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$watch"/><category scheme="http://www.blogger.com/atom/ns#" term="performances"/><title type='text'>AngularJS et performances</title><content type='html'>La plupart des applications AngularJS ne posent pas de problèmes particuliers de performances. Mais dans certains cas, où une page peut comporter un grand nombre de bindings, il va falloir y prêter attention pour éviter les ralentissements.&lt;br /&gt;
&lt;br /&gt;
Les développeurs d’AngularJS ont toujours indiqué que le framework est capable de supporter sans problème 2000 bindings dans une page web, sur une machine un peu ancienne. C’est un ordre de grandeur, un ordinateur récent avec un navigateur web rapide pourra en supporter beaucoup plus sans soucis. Par contre sur un smartphone un peu poussif, il faudra être prudent pour éviter de se retrouver avec une application pénible à utiliser.&lt;br /&gt;
&lt;br /&gt;
Ça peut paraître beaucoup, 2000 bindings, et dans la majorité des pages d’une application on sera très loin de cette limite théorique, mais avec un gros tableau on peut l’atteindre facilement. Dans le cas d’un tableau comportant 10 colonnes, avec un binding par colonne, il suffit d’afficher 200 lignes pour arriver aux 2000 bindings. Dans tous les cas où l’on va afficher tout un ensemble de données pour chaque élément d’une collection potentiellement grande, que ce soit sous la forme d’un tableau, d’une liste ou de toute autre façon, il va falloir faire attention.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h2&gt;
Fonctionnement du dirty checking et des watches&lt;/h2&gt;
Pour rafraîchir la vue dynamique créée à partir du template HTML, AngularJS utilise des watches. La méthode &lt;i&gt;$watch&lt;/i&gt; du scope associe un traitement à exécuter lorsque la valeur d’une expression est modifiée.&lt;br /&gt;
&lt;br /&gt;
A chaque événement, AngularJS parcourt tous les watches, et évalue toutes les expressions, pour déclencher si nécessaire les traitements correspondants. Quand dans le template on met une expression entre double accolades pour afficher un texte provenant du modèle de données, ou qu’on le fait avec un attribut &lt;i&gt;ng-bind&lt;/i&gt;, la directive crée un watch pour cette expression, avec du code qui rafraîchit le contenu texte de l’élément HTML si la valeur de l’expression est modifiée.&lt;br /&gt;
&lt;br /&gt;
Et bien sûr, si on le fait à l’intérieur d’un &lt;i&gt;ng-repeat&lt;/i&gt;, ça va créer autant de watches que d’occurrences dans la répétition.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Ne pas surveiller n’importe quoi&lt;/h2&gt;
L’expression d’un watch doit être très rapide à évaluer. Ça peut être un accès à une propriété d’un objet, ou le résultat d’une fonction simple, ou n’importe quel calcul très rapide.&lt;br /&gt;
&lt;br /&gt;
Il ne faut &lt;b&gt;jamais faire de watch sur une expression coûteuse à évaluer&lt;/b&gt;, car AngularJS va l’évaluer un grand nombre de fois, et ça aurait forcément un impact très négatif sur les performances de l’application. C’est vrai aussi pour les watches implicites, ceux créés par les directives, ce qui implique qu’il ne faut jamais utiliser une expression coûteuse à évaluer dans une directive qui place un watch.&lt;br /&gt;
&lt;br /&gt;
L’extension Chrome Batarang permet de visualiser les watches d’une application AngularJS, et dispose d’un onglet “&lt;i&gt;Performance&lt;/i&gt;” permettant de connaître l’impact en temps d’exécution de chaque expression utilisée dans un watch. C’est la première chose à aller voir en cas de problème de lenteur dans une application.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Mettre en cache dans le scope&lt;/h2&gt;
On peut améliorer les performances facilement en mettant en cache les résultats des évaluations complexes. Plutôt que de faire évaluer systématiquement une expression coûteuse, on va stocker son résultat dans le scope, et le recalculer seulement lorsque c’est nécessaire. Pour cela, on place dans le contrôleur un watch explicite, avec la méthode &lt;i&gt;$watch&lt;/i&gt; du scope, qui fait le calcul complexe et stocke son résultat dans le scope.&lt;br /&gt;
&lt;br /&gt;
C’est ce que j’ai fait dans &lt;a href=&quot;https://github.com/tchatel/angular-showoff&quot; target=&quot;_blank&quot;&gt;mon outil de présentation de slides&lt;/a&gt;. Le code source des slides est au format Markdown, il est parsé et converti en HTML. Mais ce code source est rarement modifié. S’il y avait des bindings avec comme expression la fonction qui convertit le source Markdown en HTML, la conversion serait faite plusieurs fois à chaque événement tel que l’appui sur une touche, et les performances seraient désastreuses. Au contraire, un simple watch explicite sur le contenu du code source va déclencher la conversion seulement lorsqu’il est modifié :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$rootScope.$watch(&#39;source.markdown&#39;, function () {
    $rootScope.slides = Presentation.parseSource($rootScope.source.markdown);
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Le résultat de la conversion est stocké dans le scope, et les bindings portent sur ce résultat, sans nécessiter de faire la conversion à chaque évaluation d’un binding.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Paginer côté client&lt;/h2&gt;
Parfois ce n’est pas suffisant, aucun watch n’est très coûteux en soi, mais c’est leur grand nombre qui pose problème. C’est le cas quand on fait une répétition sur une grosse collection.&lt;br /&gt;
&lt;br /&gt;
La solution la plus simple est alors de paginer. Si on limite les données affichées dans la page en faisant la répétition (ngRepeat) sur seulement une portion de la collection, alors les directives utilisées dans la partie répétée ne vont plus placer des watches pour tous les éléments de la collection, mais uniquement pour ceux qui sont pris en compte dans la répétition. Quand on pagine côté client, les performances d’AngularJS vont être liées au nombre d’éléments affichés simultanément, et non plus au nombre d’éléments total de la collection.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Problème du scroll infini&lt;/h2&gt;
Si on préfère une ergonomie en scroll infini à de la pagination, en limitant le nombre d’éléments sur lesquels on fait la répétition au départ, on va aussi améliorer les performances. Mais quand on va scroller, beaucoup scroller, ce nombre va augmenter de plus en plus et peut finir par poser problème. Or il n’est pas facile d’enlever les watches des éléments qui ne sont plus visibles parce qu’on a scrollé, tout en conservant un fonctionnement normal de l’ascenseur.&lt;br /&gt;
&lt;br /&gt;
Mais il y a une autre façon d’optimiser les performances, c’est de générer moins de watches, lorsque c’est possible. Souvent, les données des éléments de la collections ne seront jamais modifiées, et du coup il est inutile d’avoir des watches qui vont surveiller en les recalculant sans arrêt des expressions dont la valeur ne changera jamais.&lt;br /&gt;
&lt;br /&gt;
Imaginons qu’on veuille faire une page web comme la timeline de Twitter, qui fonctionne avec du scroll infini ; enfin pas si infini que ça d’ailleurs, mais on peut quand même scroller longtemps avant d’arriver au bout de la collection de tweets. Les données des tweets n’évoluent pas, et si on a récupéré des centaines de tweets, c’est du gâchis d’avoir des watches qui surveillent le texte des tweets ou le nom de leur auteur, ou encore l’adresse de son image de profil...&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Binding sans watch&lt;/h2&gt;
Dans un tel cas, il faudrait pouvoir faire des bindings résolus une seule fois, qui ne placent pas de watch dans le scope, et n’ont donc pas de gros impact sur les performances. Ce n’est pas possible avec les directives fournies par AngularJS, mais on peut le faire en écrivant ses propres directives.&lt;br /&gt;
&lt;br /&gt;
Ou alors on peut aussi, et c’est encore plus simple, utiliser une directive déjà écrite comme celle-ci : “&lt;a href=&quot;https://github.com/Pasvaz/bindonce&quot; target=&quot;_blank&quot;&gt;Bindonce&lt;/a&gt;”. Elle permet de faire un bind sans watch sur des données qui ne sont pas susceptibles d’évoluer. On peut même l’utiliser avec des données récupérées en asynchrone, auquel cas il y a quand même un watch temporaire, mais un seul watch sur l’objet complet, et qui est supprimé lorsque les données sont arrivées. De toute façon à l’intérieur d’une répétition, la portion du template ne sera répétée qu’une fois que les éléments de la collection auront été chargées, donc le watch temporaire est inutile.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Directive spécifique&lt;/h2&gt;
Il est aussi possible de réduire le nombre de watches en créant une directive spécifique à ce qu’on veut afficher, qui va construire la portion de template répétée en manipulant le DOM, et sans placer toute une série de watches inutiles.&lt;br /&gt;
&lt;br /&gt;
Dans l’exemple des tweets, on peut faire une directive qui affiche complètement un tweet. Mais il ne faut pas qu’elle utilise un template avec les directives standards d’AngularJS qui impliquent la pose de watches. Elle doit récupérer les données du tweet dans le scope et construire directement dans le DOM, et une fois pour toute, la portion de HTML à afficher.&lt;br /&gt;
&lt;br /&gt;
C’est faisable, mais ça demande plus de travail que d’utiliser une directive générique comme celle citée précédemment (Bindonce). Et ce n’est pas forcément beaucoup plus efficace en terme de performances.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Pas d’optimisations prématurées&lt;/h2&gt;
Dans tous les cas, il y a une règle générale concernant les optimisations, qui n’a rien de spécifique à AngularJS : il ne faut optimiser que lorsqu’il y a un problème de performances. Dans la plupart des cas tout se passe très bien, et il n’est pas souhaitable de complexifier l’application inutilement. S’il n’y a pas trop de bindings dans la page, le gain de performances serait sans doute trop faible pour que l’utilisateur s’en rende compte, et pour justifier des optimisations superflues.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/1502168598714424450/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/05/angularjs-et-performances.html#comment-form' title='4 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/1502168598714424450'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/1502168598714424450'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/05/angularjs-et-performances.html' title='AngularJS et performances'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-4778529453913451878</id><published>2013-03-22T15:15:00.000+01:00</published><updated>2013-03-22T15:21:00.810+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="communauté"/><title type='text'>Communauté AngularJS France, et rédacteurs pour FrAngular</title><content type='html'>Je me suis dit qu&#39;il manquait un espace pour les échanges et les annonces concernant AngularJS en France. Les rencontres, soirées ou conférences sur notre framework préféré commencent à se multiplier, et il devient difficile de savoir ce qui a lieu en France, ou comment avertir le public intéressé.&lt;br /&gt;
&lt;br /&gt;
Du coup je viens de créer une communauté sur Google+ intitulée - après une longue réflexion -&amp;nbsp;“&lt;a href=&quot;https://plus.google.com/communities/109984348857296908402&quot; target=&quot;_blank&quot;&gt;AngularJS France&lt;/a&gt;”. Ce n&#39;est peut-être pas hyper original, mais on moins on comprend assez vite de quoi il s&#39;agit. Elle est toute fraîche, les pixels ne sont pas secs, et j&#39;en suis encore le seul membre mais j&#39;imagine que ça ne va pas durer. Elle n&#39;a pas pour but de concurrencer la communauté AngularJS officielle, mais simplement de permettre de trouver facilement les infos locales sur le sujet.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
J&#39;y ai mis une première annonce d&#39;événement, la session AngularJS que j&#39;animerai ce mercredi (27 mars) à Devoxx France, de 13h30 à 16h30. Trois heures, ça laisse le temps de montrer plein de choses. Par contre ça représente beaucoup de préparation, et je ne pourrai sans doute pas écrire de nouvel article pour ce blog avant quelques temps, la fin du mois de mars et le début d&#39;avril s&#39;annonçant très chargés.&lt;br /&gt;
&lt;br /&gt;
S&#39;il y a des développeurs AngularJS qui ont envie d&#39;écrire des articles sur FrAngular, qu&#39;ils n&#39;hésitent pas à se manifester. Pour l&#39;instant nous ne sommes que trois à y avoir pondu quelques articles, tous les trois de Montpellier où AngularJS commence à être bien connu, et pas seulement grâce à moi puisque Benjamin n&#39;avait assisté à aucune de mes interventions lorsqu&#39;il m&#39;a contacté. Mais nous ne sommes pas sectaires, les contributeurs sont acceptés d&#39;où qu&#39;ils viennent.&lt;br /&gt;
&lt;br /&gt;
Voilà, alors à bientôt à Devoxx France pour ceux qui y vont, et sur la communauté &lt;a href=&quot;https://plus.google.com/communities/109984348857296908402&quot; target=&quot;_blank&quot;&gt;AngularJS France&lt;/a&gt; pour tout le monde.</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/4778529453913451878/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/03/communaute-angularjs-france-et.html#comment-form' title='4 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4778529453913451878'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4778529453913451878'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/03/communaute-angularjs-france-et.html' title='Communauté AngularJS France, et rédacteurs pour FrAngular'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-772332582151961454</id><published>2013-03-14T18:29:00.000+01:00</published><updated>2013-11-15T00:07:02.721+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="apprentissage"/><title type='text'>Comment aborder AngularJS ?</title><content type='html'>&lt;br /&gt;
Si vous avez lu les articles publiés sur FrAngular qui détaillent des aspects techniques du framework, c&#39;est que vous avez peut-être déjà dépassé le stade de l’initiation. Néanmoins, pour ceux qui découvrent AngularJS, je vais expliquer comment on peut aborder ce framework dont l’apprentissage risque de s’avérer quelque peu déroutant.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Commencez par JavaScript&lt;/h2&gt;
&lt;br /&gt;
JavaScript est certainement le langage le plus mal connu de ses utilisateurs de toute l’histoire de l’informatique. Combien de développeurs qui font du JavaScript ont vraiment pris le temps de l’apprendre ? On y reconnaît une syntaxe familière parce qu’on a déjà fait du Java, du C++, du PHP, enfin quelque chose qui y ressemble de très loin, et du coup en tâtonnant on arrive à écrire du code JavaScript qui fonctionne à peu près. Mais du très mauvais code JavaScript. Je suis passé par là, comme presque tout le monde.&lt;br /&gt;
&lt;br /&gt;
Les concepts de JavaScript - un langage objet sans classes et avec un forte composante fonctionnelle - sont complètement différents des autres langages objets les plus répandus. Il y a plus de ressemblance entre Java, C#, Python, Ruby, et même PHP en version objet, qu’entre n’importe lequel de ces langages et JavaScript.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
AngularJS ne requiert pas une très grande expérience de JavaScript, mais pour être à l’aise sur les exemples et le développement d’applications, il faut avoir compris les principaux concepts du langage : les fonctions et le fait qu’elles sont de vrais objets, leur utilisation comme constructeur, le mécanisme de closure, les différentes façons de créer des objets, etc.&lt;br /&gt;
&lt;br /&gt;
On m’a dit au Marseille JUG qu’AngularJS c’est du JavaScript décomplexé. J’ai bien aimé l’expression, car en effet c’est juste du JavaScript, mais du vrai JavaScript qui n’essaie pas de ressembler à un langage objet basé sur des classes. Donc si on aborde le code des applications AngularJS en ayant en tête le paradigme objet de Java, C# ou autre, il y a de quoi être perplexe.&lt;br /&gt;
&lt;br /&gt;
Mieux vaut commencer par un peu de lecture sur JavaScript, dans un bouquin ou avec un &lt;a href=&quot;http://ejohn.org/apps/learn/&quot; target=&quot;_blank&quot;&gt;tutoriel en ligne&lt;/a&gt;. AngularJS est un framework qui bouscule nos habitudes, et s’il faut en même temps comprendre JavaScript, la première marche de l’apprentissage peut être trop haute et rebuter même les plus courageux.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Le passage obligé du tutoriel officiel&lt;/h2&gt;
&lt;br /&gt;
Le &lt;a href=&quot;http://docs.angularjs.org/tutorial&quot; target=&quot;_blank&quot;&gt;tutoriel du site officiel&lt;/a&gt;&amp;nbsp;est particulièrement bien fait, et il ne faut surtout pas passer à côté. Il permet, en étant guidé pas à pas, de découvrir l’essentiel des fonctionnalités et la façon de développer une application avec AngularJS. Seule la création de ses propres directives (les composants d’AngularJS) n’est pas abordée, à raison car c’est une partie qui demande déjà une certaine compréhension du framework. Commencez par utiliser celles qui existent, avant de vouloir écrire les votres.&lt;br /&gt;
&lt;br /&gt;
En suivant toutes les étapes du tutoriel, grâce à l’utilisation judicieuse de branches Git, on découvre les différents aspects de la création d’une application mono-page, qui incluent les tests unitaires et les scénarios de tests fonctionnels.&lt;br /&gt;
&lt;br /&gt;
Donc commencez l’apprentissage directement par le tutoriel de bout en bout, plutôt que par la documentation. N’essayez pas de lire la doc avant d’avoir fait le tutoriel, beaucoup de notions qui pourraient paraître hermétiques à la lecture seront bien plus compréhensibles si vous les avez déjà rencontrées de façon concrète au travers du tutoriel.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Le guide du développeur, et autres ressources&lt;/h2&gt;
&lt;br /&gt;
Les articles du &lt;a href=&quot;http://docs.angularjs.org/guide/&quot; target=&quot;_blank&quot;&gt;guide du développeur&lt;/a&gt;, sur le site officiel, expliquent les différents aspects du framework : les contrôleurs, modules, services, l’injection de dépendances, les directives, etc. Le premier à lire est celui intitulé “&lt;a href=&quot;http://docs.angularjs.org/guide/concepts&quot; target=&quot;_blank&quot;&gt;&lt;i&gt;Conceptual Overview&lt;/i&gt;&lt;/a&gt;” qui décrit en détails le fonctionnement d’AngularJS, il peut même être lu avant d’aborder le tutoriel.&lt;br /&gt;
&lt;br /&gt;
Toute la documentation “&lt;a href=&quot;http://docs.angularjs.org/api/&quot; target=&quot;_blank&quot;&gt;API Reference&lt;/a&gt;” est comme son nom l’indique une doc de référence dans laquelle piocher pour connaître le détail de l’utilisation et du fonctionnement de chaque directive ou service fourni avec le framework. Elle contient de nombreux exemples d’utilisation. Bien sûr, même si elle est déjà volumineuse et très utile, elle reste perfectible, et toutes les questions n’y trouveront pas forcément une réponse. Il y a de nombreux échanges sur des questions techniques dans &lt;a href=&quot;https://groups.google.com/forum/?fromgroups#!forum/angular&quot; target=&quot;_blank&quot;&gt;la mailing list&lt;/a&gt; ou sur &lt;a href=&quot;http://stackoverflow.com/questions/tagged/angularjs&quot; target=&quot;_blank&quot;&gt;Stack Overflow&lt;/a&gt;. Malgré tout, même s’il ne s’agit pas de la documentation idéale dont rêve tout développeur, peu de frameworks en ont une aussi complète.&lt;br /&gt;
&lt;br /&gt;
En dehors du site officiel, il faut évidemment citer &lt;a href=&quot;http://egghead.io/&quot; target=&quot;_blank&quot;&gt;les vidéos de John Lindquist&lt;/a&gt;, de courtes séquences où il présente et explique la plupart des aspects du framework.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Oubliez jQuery&lt;/h2&gt;
&lt;br /&gt;
Il vaut mieux apprendre AngularJS sans que jQuery soit chargé. AngularJS utilise jQuery s’il est présent, mais il contient en interne une implémentation “&lt;i&gt;jQuery lite&lt;/i&gt;” contenant tout ce dont il a besoin, ce qui le rend parfaitement autonome.&lt;br /&gt;
&lt;br /&gt;
Le danger, si jQuery est présent, c’est de prendre les choses à l’envers. On ne peut pas ajouter de l’AngularJS à une application jQuery. C’est un framework très structurant, qui impose sa façon de développer une application web. Pour un développeur habitué à jQuery, ou à d’autres outils de ce type, le plus dur c’est sans doute d’oublier ce qu’il connaît, et de ne pas se raccrocher à ses habitudes.&lt;br /&gt;
&lt;br /&gt;
Le fonctionnement des vues dynamiques d’AngularJS n’a rien à voir avec les manipulations de DOM que beaucoup de développeurs web ont intégré comme un mal nécessaire, au point de finir par trouver naturel de développer ainsi une application. Toutes ces manipulations techniques qui constituent souvent la plus grande partie du code n’ont aucune raison d’être avec AngularJS. Dans une application AngularJS, on ne touche jamais au DOM. On utilise des directives, dont certaines peuvent modifier le DOM de la page web, mais la création de tels composants est la seule exception à la règle. Les contrôleurs et services ne travaillent que sur le modèle de données, ils n’ont même pas accès à la vue. Mélanger du code JavaScript ou jQuery manipulant le DOM dans les templates d’une application AngularJS, c’est une erreur qui est fréquente chez les débutants.&lt;br /&gt;
&lt;br /&gt;
Bien sûr on peut encapsuler un composant jQuery UI dans une directive AngularJS, et dans ce cas il faudra que jQuery soit chargé. Mais si on n’utilise aucune directive de ce genre, alors autant ne pas charger jQuery. L’implémentation interne à AngularJS est suffisante pour écrire ses propres directives, et ça évitera le mauvais réflexe d’utiliser jQuery ailleurs que dans une directive.&lt;br /&gt;
&lt;br /&gt;
Il y a une discussion sur Stack Overflow, intitulée &lt;a href=&quot;http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background&quot; target=&quot;_blank&quot;&gt;“&lt;i&gt;How do I ‘think in AngularJS’ if I have a jQuery background?&lt;/i&gt;”&lt;/a&gt;, qui est intéressante à lire sur le sujet. Mais je ne suis pas vraiment d’accord avec le point n°1 de la réponse, on peut très bien faire d’abord le design HTML d&#39;une page web, pour y ajouter ensuite les directives AngularJS.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Persévérez !&lt;/h2&gt;
&lt;br /&gt;
Voilà pour ces quelques conseils sur la façon d’aborder AngularJS. C’est un framework qui apporte toute l’architecture nécessaire pour développer la partie cliente d’une application web de façon très efficace. Mais de part son ampleur, il nécessite un réel apprentissage pour dépasser le stade des exemples simples.&lt;br /&gt;
&lt;br /&gt;
On peut rapidement commencer à écrire quelques lignes de jQuery, ou même faire le tour d’un framework minimal comme Backbone.js. Faire le tour d’AngularJS, c’est beaucoup plus long. Après des mois à l’utiliser, vous découvrirez encore de nouvelles possibilités. Ne soyez pas dissuadés par cet apprentissage, le gain de productivité qu’il apporte est tellement énorme que ça vaut vraiment le coup de s’y investir.</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/772332582151961454/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/03/comment-aborder-angularjs.html#comment-form' title='6 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/772332582151961454'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/772332582151961454'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/03/comment-aborder-angularjs.html' title='Comment aborder AngularJS ?'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-8579087675780878358</id><published>2013-03-05T19:01:00.000+01:00</published><updated>2013-03-05T19:01:30.567+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="expression"/><title type='text'>Le langage d&#39;expressions d&#39;AngularJS</title><content type='html'>Voici un article qui tient du paradoxe, puisque je vais y décrire les possibilités du langage d&#39;expressions d&#39;AngularJS, tout en expliquant qu&#39;il faut les utiliser au minimum.
&lt;br /&gt;
&lt;br /&gt;
Avec AngularJS, on utilise des expressions pour le binding, et avec de nombreuses directives. Elles ressemblent à des expressions JavaScript, mais n&#39;en sont pas. Elles ne sont pas directement évaluées par l&#39;interpréteur JavaScript, mais parsées et exécutées par AngularJS. Du coup on ne peut utiliser qu&#39;un sous-ensemble des opérateurs et des mots-clefs de JavaScript, et elles divergent aussi de JavaScript sur quelques aspects.
&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h2&gt;
Garder les expressions simples&lt;/h2&gt;
&lt;br /&gt;
La règle importante, c&#39;est de ne pas mettre de code dans les vues. Le code mis dans les vues ne peut pas faire l&#39;objet de tests unitaires, et est difficilement gérable.&lt;br /&gt;
&lt;br /&gt;
Donc dans les expressions, il faut se limiter à du binding sur des propriétés des objets du scope, à des appels de fonctions du scope, à des comparaisons simples quand il s&#39;agit d&#39;expressions booléennes, à des choses basiques de ce genre, et ne jamais y mettre du code non trivial qui mérite d&#39;être testé. Il vaut mieux remplacer les expressions complexes par des appels à des fonctions qu&#39;un contrôleur publie dans son scope.
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Les mots-clefs et opérateurs disponibles&lt;/h2&gt;
&lt;br /&gt;
La liste des mots-clefs utilisables est particulièrement courte, puisqu&#39;ils sont au nombre de quatre, et leur rôle est le même qu&#39;en JavaScript :
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;true&lt;/li&gt;
&lt;li&gt;false&lt;/li&gt;
&lt;li&gt;undefined&lt;/li&gt;
&lt;li&gt;null&lt;/li&gt;
&lt;/ul&gt;
Celle des opérateurs disponibles est plus fournie, et là encore ils fonctionnent comme en JavaScript :
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;+ &amp;nbsp;- &amp;nbsp;* &amp;nbsp;/ &amp;nbsp;%&lt;/li&gt;
&lt;li&gt;( &amp;nbsp;)&lt;/li&gt;
&lt;li&gt;== &amp;nbsp;!= &amp;nbsp;&amp;lt; &amp;nbsp; &amp;gt; &amp;nbsp;&amp;lt;= &amp;nbsp;&amp;gt;=&lt;/li&gt;
&lt;li&gt;!&lt;/li&gt;
&lt;li&gt;&amp;amp;&amp;amp; &amp;nbsp;||&lt;/li&gt;
&lt;li&gt;=== &amp;nbsp;!== &amp;nbsp;&lt;span style=&quot;color: #990000;&quot;&gt;en version 1.1.2&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;= &lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(affectation d&#39;une variable)&lt;/span&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
Quand je dis que ces opérateurs fonctionnent comme en JavaScript, c&#39;est tout simplement parce que l&#39;évaluateur d&#39;AngularJS appelle l&#39;opérateur JavaScript correspondant sur les opérandes. Donc il y a très peu de chances pour que le résultat soit différent.&lt;br /&gt;
&lt;br /&gt;
Du coup les opérateurs &lt;b&gt;&amp;amp;&amp;amp;&lt;/b&gt;&amp;nbsp;et &lt;b&gt;||&lt;/b&gt;&amp;nbsp;vont comme en JavaScript court-circuiter l&#39;évaluation dès que c&#39;est possible, et renvoyer la valeur du dernier opérande évalué, donc potentiellement tout autre chose qu&#39;une valeur booléenne.
&lt;br /&gt;
&lt;br /&gt;
Les opérateurs de comparaison stricte, c&#39;est-à-dire sans conversion automatique de type, ont été ajouté en version 1.1.2, mais ils ne sont pas disponibles dans la branche 1.0.
&lt;br /&gt;
&lt;br /&gt;
L&#39;opérateur d&#39;affectation permet d&#39;affecter une valeur à une propriété du scope. En règle générale, ce n&#39;est pas une bonne idée de publier des choses dans le scope depuis une expression de la vue. Mais il y a quand même quelques cas où c&#39;est tolérable, par exemple pour effacer la valeur d&#39;un champ de filtrage en cliquant sur un lien, une petite croix à côté du champ. On peut préférer faire ça avec un &lt;span style=&quot;color: #134f5c; font-family: Courier New, Courier, monospace;&quot;&gt;ng-click=&quot;search = &#39;&#39;&quot;&lt;/span&gt;, sans avoir envie de créer une fonction dans le scope juste pour ça.
&lt;br /&gt;
&lt;br /&gt;
Il y a un opérateur classique qui manque à la liste, c&#39;est l&#39;opérateur conditionnel ternaire ... ? ... : ... qui n&#39;est effectivement pas disponible dans le langage d&#39;expressions d&#39;AngularJS. Il y a eu des discussions sur le fait de le rajouter, ça n&#39;a pas été fait du moins pour l&#39;instant, l&#39;idée étant que cet opérateur qui s&#39;apparente à un &lt;i&gt;if&lt;/i&gt;&amp;nbsp;pourrait inciter &amp;nbsp;les développeurs à mettre trop de logique dans les expressions. On peut contourner cette absence en utilisant une des deux constructions suivantes :
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;condition &amp;amp;&amp;amp; valeurTrue || valeurFalse
{true: valeurTrue, false: valeurFalse}[condition]&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Mais ce n&#39;est pas forcément recommandé, le réflexe devrait être plutôt de créer une fonction dans le scope que d&#39;utiliser une de ces deux syntaxes alambiquées.
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Propriétés et méthodes&lt;/h2&gt;
&lt;br /&gt;
Tous les noms d&#39;identifiants utilisés dans des expressions AngularJS sont recherchés comme des propriétés du scope. C&#39;est la première chose qu&#39;on utilise dans les bindings, je ne vous apprends rien. On peut utiliser le point ou les crochets pour accéder à une propriété d&#39;une objet, ou les crochets uniquement pour accéder à un élément d&#39;un tableau par son index, exactement comme en JavaScript.
&lt;br /&gt;
&lt;br /&gt;
La grosse différence, c&#39;est que si dans une expression on essaie d&#39;accéder à une propriété d&#39;un objet nul ou non défini, ça ne provoque aucune erreur. Pareil pour un élément d&#39;un tableau nul ou non défini. Et il est indispensable que ça fonctionne comme ça : une vue ne plante pas si des données ne sont pas encore chargées, simplement ces données apparaîtront lorsqu&#39;elles seront disponibles.
&lt;br /&gt;
&lt;br /&gt;
La contrepartie du fait que ça ne provoque aucune erreur, c&#39;est que si l&#39;on a réellement fait une erreur, par exemple une faute de frappe, on ne verra rien. Raison de plus de ne pas mettre de code dans les expressions des vues, car le debug n&#39;y est pas pratique.
&lt;br /&gt;
&lt;br /&gt;
On peut bien sûr accéder à n&#39;importe quelle propriété d&#39;un objet JavaScript, par exemple à la propriété &lt;i&gt;length&lt;/i&gt;&amp;nbsp;d&#39;un tableau, ou appeler n&#39;importe quelle méthode.
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Filtres&lt;/h2&gt;
&lt;br /&gt;
Là évidemment c&#39;est quelque chose qui n&#39;existe pas dans des expression JavaScript. Les filtres sont propres à AngularJS. Si le symbole utilisé pour les filtres est la barre verticale, ce n&#39;est pas sans rapport avec les&amp;nbsp;“&lt;i&gt;pipes&lt;/i&gt;” d&#39;Unix : un filtre prend des données en entrée, et fournit des données en sortie. Il peut aussi recevoir des paramètres, indiqués à la suite du nom du filtre, séparées par le caractère &#39;&lt;b&gt;:&lt;/b&gt;&#39;. Et on peut chaîner plusieurs filtres.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;data | filterName:param1:param2 | otherFilter&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2&gt;
Création d&#39;objets et tableaux&lt;/h2&gt;
&lt;br /&gt;
Dans une expression AngularJS, on peut créer un objet ou un tableau comme on le ferait en JavaScript, avec {...} ou [...].
&lt;br /&gt;
&lt;br /&gt;
Là encore c&#39;est à utiliser avec parcimonie, mais ça peut quand même être très utile pour alimenter les paramètres d&#39;une fonction (ou d&#39;un filtre) qui attend un objet ou un tableau.
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;list | paginate:{index: currentPage, size: pageSize}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Priorités&lt;/h2&gt;
Les priorités des opérateurs ne sont pas déroutantes puisque ce sont les mêmes qu&#39;en JavaScript. On peut juste se demander à quel niveau de priorité se classe le filtre.&lt;br /&gt;
&lt;br /&gt;
Et bien voici un tableau des opérateurs triés par priorité, des plus prioritaires aux moins prioritaires, réalisé d&#39;après le code du framework :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;style type=&quot;text/css&quot;&gt;
.tablePrio {
  font-size: 0.9em;
}
.tablePrio td,th {
  padding: 3px 8px 3px 8px;
  border-bottom: 1px solid grey;
  background-color: #F4F4F4;
}
.tablePrio td.remark {
  font-style: italic;
}
.tablePrio td.bold{
  font-weight: bold;
}
.tablePrio th {
  font-weight: bold;
  text-align: center;
  vertical-align: bottom;
  background-color: #E0F0F0;
  border-bottom: 2px solid grey;
}
&lt;/style&gt;

&lt;br /&gt;
&lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tablePrio&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Opérateurs&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;. &amp;nbsp;[...] &amp;nbsp;(...)&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;propriété d&#39;un objet, élément d&#39;un tableau, appel de méthode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;(...) &amp;nbsp;[...] &amp;nbsp;{...}&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;parenthèses pour grouper des opérations, création d&#39;un tableau ou d&#39;un objet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;! &amp;nbsp;- &amp;nbsp;+&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;opérateurs + et - unaires (ex : -5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;* &amp;nbsp;/ &amp;nbsp;%&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;+ &amp;nbsp;-&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;opérateurs + et - binaires (ex : 8 - 5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;&amp;lt; &amp;nbsp;&amp;gt; &amp;nbsp;&amp;lt;= &amp;nbsp;&amp;gt;=&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;== &amp;nbsp;!= &amp;nbsp;=== &amp;nbsp;!==&amp;nbsp;&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;en version 1.1.2 pour === et !==&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;&amp;amp;&amp;amp;&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;||&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;=&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;affectation d&#39;une variable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;|&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;filtres&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;bold&quot;&gt;;&lt;/td&gt;
&lt;td class=&quot;remark&quot;&gt;séparateur d&#39;instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;br /&gt;
On voit donc que le filtre est quasiment ce qu&#39;il y a de moins prioritaire, encore moins que l&#39;opérateur d&#39;affectation (mais je ne suis pas sûr que mélanger filtres et affectations soit une grande idée).&lt;br /&gt;
&lt;br /&gt;
Quasiment, parce que le point-virgule séparant deux instructions est encore moins prioritaire que le filtre. Mais franchement je ne vois pas quel bon usage peut-être fait d&#39;un séparateur d&#39;instructions dans une expression AngularJS. D&#39;ailleurs un commentaire dans le code laisse entendre qu&#39;il pourrait être supprimé.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Et s&#39;il manque quelque chose ?&lt;/h2&gt;
&lt;br /&gt;
Pour ajouter une fonctionnalité utilisable dans les expressions, vous avez deux possibilités :&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;publier une fonction dans un scope&lt;/li&gt;
&lt;li&gt;créer un filtre&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;
Si c&#39;est pour un usage bien localisé, il vaut mieux publier une fonction dans le scope d&#39;un contrôleur, et vous pourrez l&#39;utiliser seulement dans ce scope-là.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Si le besoin est général, faites un filtre, ce n&#39;est pas vraiment plus compliqué à créer qu&#39;une simple fonction JavaScript, et il sera disponible dans toutes les expressions de toutes les vues.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
Compiler une expression&lt;/h2&gt;
&lt;br /&gt;
Le service &lt;b&gt;$parse&lt;/b&gt;&amp;nbsp;intégré au framework&amp;nbsp;permet de compiler une expression AngularJS, ça la transforme en une fonction qui peut être exécutée en lui passant le scope. C&#39;est quelque chose qui peut être utile lors de l&#39;écriture de directives.&lt;br /&gt;
&lt;br /&gt;
Mais attention à ne pas réinventer la roue. Quand il est pertinent de créer un scope isolé pour une directive, alors c&#39;est plus simple de définir des propriétés dans ce scope isolé en utilisant les opérateurs préfixes @, = et &amp;amp; pour référencer des attributs de l&#39;élément HTML. Dans un tel cas on n&#39;aura pas besoin de compiler manuellement les attributs contenant des expressions, le framework s&#39;en charge.&lt;br /&gt;
&lt;br /&gt;
Je crois que le paragraphe précédent doit ressembler à du chinois pour ceux qui n&#39;ont jamais écrit de directives. Il faudra que j&#39;écrive quelques articles là-dessus, mais le sujet est vaste, et ça sera pour une autre fois.&lt;br /&gt;
&lt;br /&gt;
En tout cas j&#39;espère que celui-ci aura permis de clarifier les idées sur le langage d&#39;expressions d&#39;AngularJS.&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/8579087675780878358/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/03/langage-expressions-angularjs.html#comment-form' title='2 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/8579087675780878358'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/8579087675780878358'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/03/langage-expressions-angularjs.html' title='Le langage d&#39;expressions d&#39;AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-5672949634779099664</id><published>2013-02-28T12:13:00.002+01:00</published><updated>2013-02-28T12:13:34.360+01:00</updated><title type='text'>AngularJS, ce n&#39;est pas pour jouer</title><content type='html'>&lt;br /&gt;
De nombreux messages ont circulé hier sur Twitter annonçant un jeu en ligne, &lt;a href=&quot;http://bombermine.com/&quot; target=&quot;_blank&quot;&gt;Bombermine&lt;/a&gt;, réalisé avec AngularJS. Étonnant... mais erroné. J’ai moi-même rediffusé un de ces messages, avant de regarder de plus près et de voir qu’AngularJS n’est utilisé que pour l’interface utilisateur, mais que le jeu lui-même est écrit avec GWT.&lt;br /&gt;
&lt;br /&gt;
AngularJS est particulièrement efficace pour la réalisation d’application web, mais pas du tout adapté à la création de jeux.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
CRUD... au sens large&lt;/h2&gt;
&lt;br /&gt;
Les développeurs d’AngularJS le présentent comme un framework destiné à la réalisation d’applications de type “CRUD” (&lt;i&gt;Create - Read - Update - Delete&lt;/i&gt;), c’est-à-dire des applications web affichant des données, et permettant de les saisir ou de les modifier.&lt;br /&gt;
&lt;br /&gt;
C’est effectivement le cas, si l’on ne prend pas le terme CRUD dans un sens trop restrictif, voire péjoratif. Il ne faut pas imaginer des applications réduites à l’affichage de trois pauvres formulaires, bien au contraire.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
Le cœur d’AngularJS, c’est son système de vues dynamiques rafraîchies automatiquement, et fonctionnant par du data-binding à double sens. Les vues d’AngularJS constituent la représentation d’un état des données du modèle, et c’est le critère qui permet de voir quand ce framework est adapté. Il est pertinent pour toutes les applications dont l’interface est essentiellement une représentation de données facilement modélisables sous la forme d’objets JavaScript.&lt;br /&gt;
&lt;br /&gt;
Mais ce n’est pas forcément un simple affichage de texte ou de formulaires, la représentation peut être bien plus sophistiquée que ça, on peut avoir des graphiques, des cartes géographiques, des schémas en SVG, comme dans ces quelques exemples :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://plnkr.co/edit/kGUhDM?p=preview&quot; target=&quot;_blank&quot;&gt;graphique de type camembert&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://nlaplante.github.com/angular-google-maps/#!/demo&quot; target=&quot;_blank&quot;&gt;cartographie avec Google Maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://sullerandras.github.com/SVG-Sequence-Diagram/&quot; target=&quot;_blank&quot;&gt;diagramme de séquence en SVG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
Ces exemples présentent des utilisations d’AngularJS très pertinentes, car il s’agit bien de représentation de données, où le principe de binding dans les vues dynamiques facilite largement la tâche du développeur. En fait le type d&#39;application CRUD où AngularJS est très efficace couvre la quasi-totalité des applications de gestion.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Quand AngularJS n’est pas approprié&lt;/h2&gt;
&lt;br /&gt;
Là où AngularJS est déconseillé, c’est quand l’application web ne représente pas directement des données, quand on ne peut pas créer la vue par des bindings simples sur un ensemble d’objets JavaScript.&lt;br /&gt;
&lt;br /&gt;
L’exemple que cite généralement Miško Hevery, c’est Google Documents. L’interface de traitement de texte de Google Documents est beaucoup plus complexe qu’une simple vue HTML représentant l’état d’un modèle de données. C’est vraiment un cas où AngularJS n’apporterait rien au développeur, et serait même totalement inapproprié.&lt;br /&gt;
&lt;br /&gt;
L’autre cas typique, c’est la création de jeux. Bon évidemment, il y a quelques jeux où ce qui s’affiche est une représentation assez simple d’un état des données. Un jeu d’échecs par exemple : l’échiquier affichant la position des pièces, c’est juste la représentation d’un tableau à deux dimensions en JavaScript ; pour la partie IA, c’est une toute autre histoire... Mais sur des jeux un peu plus animés, la part de binding sur les données devient minime par rapport à la complexité du dessin et des animations, et essayer d’utiliser AngularJS serait une erreur.&lt;br /&gt;
&lt;br /&gt;
Pour des adaptations de jeux de plateau, jeux de cartes ou wargames, AngularJS peut être pertinent, et on peut imaginer un système du type de &lt;a href=&quot;http://www.vassalengine.org/&quot; target=&quot;_blank&quot;&gt;Vassal&lt;/a&gt; basé sur AngularJS. Etant un fervent adepte de ce genre de jeux, je creuserai peut-être cette idée un de ces jours.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
More fun ?&lt;/h2&gt;
&lt;br /&gt;
Bon d’accord, AngularJS n’est pas fait pour créer des jeux, mais ça peut quand même être amusant à utiliser. Le premier slogan qui était imprimé sur les tee-shirts à l’époque où AngularJS était en beta et que son logo de super-héros n’avait pas encore été créé, c’était “&lt;i&gt;10x less code, 10x more fun&lt;/i&gt;”. Certes le dix fois moins de code était un peu exagéré, mais pas tant que ça, et l’aspect amusant voire ludique mis en avant est bien réel.&lt;br /&gt;
&lt;br /&gt;
Le haut niveau d’abstraction d’AngularJS fait qu’on écrit vraiment la logique de présentation de son application web, au lieu d&#39;accumuler la frustration en passant son temps à bricoler le DOM pour essayer d’arriver à faire marcher quelque chose. Le côté gratifiant pour les développeurs, c’est un aspect très important à prendre en compte quand on choisit une plate-forme, car c’est ce qui les motive les plus.&lt;br /&gt;
&lt;br /&gt;
Qui sait, peut-être qu’un jour on verra dans l’informatique des syndicats qui revendiqueront de travailler avec AngularJS.&lt;br /&gt;
</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/5672949634779099664/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/02/angularjs-ce-nest-pas-pour-jouer.html#comment-form' title='1 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/5672949634779099664'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/5672949634779099664'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/02/angularjs-ce-nest-pas-pour-jouer.html' title='AngularJS, ce n&#39;est pas pour jouer'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-2991636414651778452</id><published>2013-02-19T08:00:00.000+01:00</published><updated>2013-02-20T09:51:10.176+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$compile"/><category scheme="http://www.blogger.com/atom/ns#" term="$watch"/><category scheme="http://www.blogger.com/atom/ns#" term="module"/><category scheme="http://www.blogger.com/atom/ns#" term="service"/><title type='text'>Les fonctionnalités de Twig avec AngularJS: angular-twig-pack</title><content type='html'>Depuis que je travaille avec AngularJS, j&#39;ai entendu beaucoup de gens comparer le Framework avec divers moteurs de template... Et il en existe! La liste (non-exhaustive) de wikipedia: &lt;a href=&quot;http://en.wikipedia.org/wiki/Template_engine_(web)&quot; target=&quot;_blank&quot;&gt;Template_engine_(web)&lt;/a&gt;&amp;nbsp;en est la preuve. Malheureusement, Wikipedia ne nous numérote pas ce tableau, heureusement que la console est là:
&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;border-bottom: 1px solid #DDDDDD; margin-bottom: 0;&quot;&gt;
&lt;span style=&quot;color: #666666; font-weight: bold;&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #0099cc;&quot;&gt;document.querySelectorAll(&#39;.wikitable tbody tr&#39;).length&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;color: #00006b; margin-top: 0; padding-left: 12px;&quot;&gt;
89&lt;/div&gt;
&lt;br /&gt;
Bon ... Trêve de plaisanterie, passons aux choses sérieuses. Je me suis dit pourquoi ne pas prendre un moteur de template existant et créer un pack de directives, filters et services pour ne pas trop dépayser les puristes.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Kezako&lt;/h2&gt;
&lt;div&gt;
Pour cet exercice, j&#39;ai choisi le Framework Twig (uniquement car je l&#39;ai déjà utilisé et surtout car j&#39;adore les couleurs du site:&amp;nbsp;http://twig.sensiolabs.org/).&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/div&gt;
Du coup, j&#39;ai créé un projet GitHub : &lt;a href=&quot;http://firehist.github.com/angular-twig-pack/&quot; target=&quot;_blank&quot;&gt;angular-twig-pack&lt;/a&gt;. J&#39;ai également fait une démo disponible sur le repo pour tester les différentes fonctionnalités. Le projet n&#39;a rien d&#39;extraordinaire niveau programmation, c&#39;est uniquement un pack qui montre qu&#39;AngularJS peut facilement être adopté par tous les développeurs ... avec un peu de bonne volonté :-D&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Les modules&lt;/h2&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Pour regrouper et organiser les différentes fonctionnalités de ce pack, j&#39;ai utiliser les modules d&#39;AngularJS. La notion de module est vraiment très intéressante pour notre cas. Elle permet de regrouper plusieurs modules en un seul &lt;i&gt;twig&lt;/i&gt;&amp;nbsp;ce qui rend modulaire notre développement.&lt;/div&gt;
&lt;div&gt;
Je me suis largement inspiré des différents packs disponible pour AngularJS : &lt;a href=&quot;http://angular-ui.github.com/&quot; target=&quot;_blank&quot;&gt;AngularUI&lt;/a&gt;&amp;nbsp;et&amp;nbsp;&lt;a href=&quot;http://angular-ui.github.com/bootstrap/&quot; target=&quot;_blank&quot;&gt;AngularUI Bootstrap&lt;/a&gt;&amp;nbsp;entre autres.&lt;/div&gt;
Je vous invite à lire ce bref article qui résume assez bien les modules AngularJS, leurs fonctionnements et leurs rôles: &lt;a href=&quot;http://www.itaware.eu/2012/10/19/angularjs-modules-and-services/&quot; target=&quot;_blank&quot;&gt;I.T. Aware&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://github.com/firehist/angular-twig-pack&quot; target=&quot;_blank&quot;&gt;angular-twig-pack&lt;/a&gt;&amp;nbsp;implémente des filters, des directives et un service. Ces différentes entités contiennent et mettent à disposition les fonctionnalités du Moteur de Template Twig que j&#39;ai trouvées intéressantes d&#39;intégrer.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
La directive for&lt;/h2&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Cette directive est assez sympathique car elle intègre quelques contraintes.&lt;/div&gt;
&lt;div&gt;
La première est celle d&#39;être à l&#39;écoute des différentes variables passées en paramètre. Il faut pour cela utiliser la méthode&amp;nbsp;&lt;b&gt;$watch&lt;/b&gt;&amp;nbsp;disponible dans l&#39;API d&#39;AngularJS. Cette méthode permet d&#39;écouter tous changements sur une variable afin d&#39;exécuter une méthode. Dans notre cas, de recalculer les conditions et les variables du for.&lt;/div&gt;
&lt;div&gt;
La seconde vient du fait que l&#39;on peut dans l&#39;expression de la directive définir à la fois des variables du scope, une expression qui se traduit par un &lt;i&gt;range&lt;/i&gt;&amp;nbsp;et l&#39;utilisation possible de filter dans cette expression. Pour fonctionner, j&#39;ai utilisé les expressions régulières (un petit outil en ligne pour tester les regex:&amp;nbsp;&lt;a href=&quot;http://www.pagecolumn.com/tool/regtest.htm&quot;&gt;http://www.pagecolumn.com/tool/regtest.htm&lt;/a&gt;) ainsi que la méthode &lt;b&gt;$compile&lt;/b&gt;&amp;nbsp;d&#39;AngularJS, qui permet d&#39;utiliser pleinement le moteur de template.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;
La démo! La démo!&lt;/h2&gt;
&lt;div&gt;
Dernière petite chose avant la démo, la liste des fonctionnalités de ce pack.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Filters:&lt;/h3&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;url_encode&lt;/li&gt;
&lt;li&gt;json_decode&lt;/li&gt;
&lt;li&gt;title&lt;/li&gt;
&lt;li&gt;capitalize&lt;/li&gt;
&lt;li&gt;upper&lt;/li&gt;
&lt;li&gt;striptags&lt;/li&gt;
&lt;li&gt;join&lt;/li&gt;
&lt;li&gt;reverse&lt;/li&gt;
&lt;li&gt;length&lt;/li&gt;
&lt;li&gt;sort&lt;/li&gt;
&lt;li&gt;default&lt;/li&gt;
&lt;li&gt;keys&lt;/li&gt;
&lt;li&gt;escape&lt;/li&gt;
&lt;li&gt;abs&lt;/li&gt;
&lt;li&gt;nl2br&lt;/li&gt;
&lt;li&gt;number_format&lt;/li&gt;
&lt;li&gt;slice&lt;/li&gt;
&lt;li&gt;trim&lt;/li&gt;
&lt;li&gt;divisibleby&lt;/li&gt;
&lt;li&gt;even&lt;/li&gt;
&lt;li&gt;odd&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;h3&gt;
Directives:&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;for&lt;/li&gt;
&lt;li&gt;if (if/elseif/else)&lt;/li&gt;
&lt;li&gt;macro (macroSet et macroGet)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;h3&gt;
Service:&lt;/h3&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;twig.random&lt;/li&gt;
&lt;li&gt;twig.divisibleby&lt;/li&gt;
&lt;li&gt;twig.even&lt;/li&gt;
&lt;li&gt;twig.odd&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
Enfin la démo! Bon alors pour les gens qui ne veulent pas cloner le projet et le lancer en local, voici le lien de la démo:&amp;nbsp;&lt;a href=&quot;http://demo.firehist.org/FRAngular_angular-twig-pack/demos&quot; target=&quot;_blank&quot;&gt;FRAngular_angular-twig-pack&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
GitHub:&amp;nbsp;&lt;a href=&quot;http://firehist.github.com/angular-twig-pack/&quot;&gt;http://firehist.github.com/angular-twig-pack/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
J&#39;attends vos retours, remarques, suggestions, etc. :-D&lt;br /&gt;
&lt;br /&gt;
Bon dév&#39;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/2991636414651778452/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/02/les-fonctionnalites-de-twig-avec.html#comment-form' title='3 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/2991636414651778452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/2991636414651778452'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/02/les-fonctionnalites-de-twig-avec.html' title='Les fonctionnalités de Twig avec AngularJS: angular-twig-pack'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/01293399064543705328</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-4309776650379939116</id><published>2013-02-13T10:37:00.000+01:00</published><updated>2013-02-13T10:37:00.157+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$watch"/><category scheme="http://www.blogger.com/atom/ns#" term="directive"/><title type='text'>Un outil de présentation en AngularJS</title><content type='html'>Pour une fois je ne vais pas vous détailler un aspect technique du framework, mais présenter une petite application qui peut être utile. Mais si je la présente ici, c&#39;est&amp;nbsp;bien sûr&amp;nbsp;parce qu&#39;elle est écrite avec AngularJS, et qu&#39;il y a quelques exemples dans le code qui peuvent vous intéresser.&lt;br /&gt;
&lt;br /&gt;
J&#39;avais besoin d&#39;un outil pour présenter des slides sur AngularJS en début de semaine prochaine : &lt;b&gt;lundi 18 février à LyonJS&lt;/b&gt;, et &lt;b&gt;mardi 19 février au Marseille JUG&lt;/b&gt;. Bien sûr il existe maintenant des outils pour faire des présentations en HTML avec plein d&#39;effets spéciaux impressionnants. Mais je dois être de la vieille école, parce que j&#39;ai toujours l&#39;impression que ces effets qui en mettent plein la vue détournent l&#39;attention du contenu. En fait je ne mets jamais d&#39;animations dans mes présentations ; sans pousser aussi loin le minimalisme, je penche plus vers la “&lt;a href=&quot;http://presentationzen.blogs.com/presentationzen/2005/10/the_lessig_meth.html&quot; target=&quot;_blank&quot;&gt;méthode Lessig&lt;/a&gt;”. Donc tout ce dont j&#39;avais besoin c&#39;était d&#39;un outil comme &lt;a href=&quot;https://github.com/schacon/showoff&quot; target=&quot;_blank&quot;&gt;ShowOff&lt;/a&gt;, permettant d&#39;écrire rapidement les styles dans une syntaxe &lt;i&gt;markdown&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
Mais ShowOff c&#39;est une application serveur, en Ruby, avec une liste de dépendances presque aussi longue que le bras. Du coup j&#39;ai écrit ce week-end une version simplifiée avec AngularJS, une simple application cliente sans côté serveur. Je l&#39;ai mise sur GitHub : &lt;a href=&quot;https://github.com/tchatel/angular-showoff&quot; target=&quot;_blank&quot;&gt;angular-showoff&lt;/a&gt;. Il y a un lien vers la démo dans le descriptif.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h2&gt;
Utilisation d&#39;un $watch&lt;/h2&gt;
Je peux vous montrer quelques trucs intéressants dans le code. D&#39;abord comme c&#39;est une application AngularJS, on peut modifier le code source markdown des slides (appuyez sur &#39;s&#39; dans la démo), et l&#39;affichage est rafraîchi automatiquement.&amp;nbsp;Ça ne sert à rien à l&#39;usage, sauf éventuellement pour rectifier une erreur à la volée pendant la présentation, mais c&#39;est toujours impressionnant !&lt;br /&gt;
&lt;br /&gt;
Ça marche grâce à un $watch, qui permet de parser le code source à chaque fois qu&#39;il est modifié :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    $rootScope.$watch(&#39;source.markdown&#39;, function () {
        $rootScope.slides = Presentation.parseSource($rootScope.source.markdown);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Il aurait été possible de faire le binding sur une fonction qui parse le code markdown, mais ça n&#39;aurait pas été une bonne idée, car AngularJS l&#39;aurait exécutée sur chaque évènement, comme un changement de slide. Alors qu&#39;avec un $watch et en stockant le résultat dans le scope, ça permet de parser seulement lorsque c&#39;est nécessaire.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Compilation d&#39;un template depuis une propriété&lt;/h2&gt;
Comme j&#39;avais besoin de cet outil pour présenter des slides sur AngularJS, je voulais pouvoir montrer des exemples fonctionnels directement à l&#39;intérieur des slides, au moins pour des exemples simples. Il y a un&amp;nbsp;“Hello Word” sur la dernière slide de la démo, si vous voulez voir.&lt;br /&gt;
&lt;br /&gt;
Pour ça, il fallait qu&#39;AngularJS compile le contenu des slides, comme avec un &amp;lt;ng-include&amp;gt;. Mais la directive &lt;i&gt;ngInclude&lt;/i&gt; ne permet pas de charger le code du template à inclure depuis une propriété du scope. Du coup j&#39;ai utilisé une directive &lt;i&gt;includeContent&lt;/i&gt;&amp;nbsp;qui est une copie légèrement modifiée de &lt;i&gt;ngInclude&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
J&#39;ai une autre idée pour faire ça, qui pourrait être plus simple, mais il faudra que je fasse quelques essais parce que je ne sais pas si c&#39;est réalisable ; ça sera pour plus tard.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Directive correspondant à un vrai élément HTML&lt;/h2&gt;
Il restait juste un problème à régler. Je voulais mettre de la coloration syntaxique sur les blocs de code, en utilisant &lt;i&gt;google-code-prettify&lt;/i&gt;, lequel a besoin d&#39;une classe&amp;nbsp;“&lt;i&gt;prettyprint&lt;/i&gt;” sur les éléments &amp;lt;pre&amp;gt; à colorer, qui n&#39;est évidement pas présente dans le HTML généré par le parser markdown utilisé. Et il faut aussi déclencher une méthode&amp;nbsp;prettyPrint() s&#39;il y a de tels blocs à décorer.&lt;br /&gt;
&lt;br /&gt;
J&#39;ai utilisé une solution toute simple, à laquelle on ne pense pas forcément. Quand on pense aux directives AngularJS, on imagine ajouter de nouveaux attributs ou éléments au HTML. Mais on peut aussi créer une directive qui s&#39;applique à des éléments standards du HTML. Il me suffisait donc de faire une directive nommée “&lt;i&gt;pre&lt;/i&gt;”, pour qu&#39;elle soit déclenchée sur tous les éléments &amp;lt;pre&amp;gt; générés par le parser dans les slides, puisque le code HTML des slides est compilé par AngularJS.&lt;br /&gt;
&lt;br /&gt;
Voici le code de cette directive :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;module.directive(&#39;pre&#39;, [&#39;$rootScope&#39;, &#39;$timeout&#39;, function($rootScope, $timeout) {
    var prettyPrintTriggered = false;
    return {
        restrict: &#39;E&#39;,
        terminal: true,  // Prevent AngularJS compiling code blocks
        compile: function(element, attrs) {
            if (!attrs[&#39;class&#39;]) {
                attrs.$set(&#39;class&#39;, &#39;prettyprint&#39;);
            } else if (attrs[&#39;class&#39;] &amp;amp;&amp;amp; attrs[&#39;class&#39;].split(&#39; &#39;)
                                                .indexOf(&#39;prettyprint&#39;) == -1) {
                attrs.$set(&#39;class&#39;, attrs[&#39;class&#39;] + &#39; prettyprint&#39;);
            }
            return function(scope, element, attrs) {
                if (!prettyPrintTriggered) {
                    prettyPrintTriggered = true;
                    $timeout(function () {
                        prettyPrintTriggered = false;
                        prettyPrint();
                    });
                }
            };
        }

    };
}]);
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Elle rajoute systématiquement la classe CSS&amp;nbsp;“prettyprint”, et elle programme avec un $timeout le déclenchement de &lt;i&gt;google-code-prettify&lt;/i&gt;, s&#39;il n&#39;a pas déjà été programmé, car s&#39;il y a plusieurs blocs de code dans une slide, il ne faut l&#39;exécuter qu&#39;une fois.&lt;br /&gt;
&lt;br /&gt;
Pouvoir ajouter ainsi un comportement à n&#39;importe quel tag HTML, ce n&#39;est pas forcément d&#39;un usage très classique, mais je suis sûr que dans certains cas ça peut être très utile, dans des cas comme celui-ci où l&#39;on n&#39;a pas la main sur le code HTML.&lt;br /&gt;
&lt;br /&gt;
Dans le fichier des directives, vous trouverez aussi celle qui sert aux évènements clavier, pour changer de slide, afficher l&#39;aide, le footer, etc. Elle n&#39;a rien de très mystérieux.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
***&lt;/div&gt;
&lt;br /&gt;
&lt;i&gt;Et pour les lecteurs de Lyon et Marseille, rendez-vous en début de semaine prochaine pour une soirée complète sur AngularJS !&lt;/i&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/4309776650379939116/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/02/un-outil-de-presentation-en-angularjs.html#comment-form' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4309776650379939116'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/4309776650379939116'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/02/un-outil-de-presentation-en-angularjs.html' title='Un outil de présentation en AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-6499642639613620165</id><published>2013-02-08T14:48:00.000+01:00</published><updated>2013-02-08T14:48:09.872+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$http"/><category scheme="http://www.blogger.com/atom/ns#" term="$resource"/><category scheme="http://www.blogger.com/atom/ns#" term="promise"/><title type='text'>Solution simple pour des formulaires avec données différées</title><content type='html'>Je ne suis pas sûr d&#39;avoir trouvé un titre très vendeur pour cet article, mais vous y trouverez un exemple qui peut être très utile. C&#39;est une solution hybride entre les &lt;b&gt;$resource&lt;/b&gt; et les &lt;b&gt;promises&lt;/b&gt; du service $q d&#39;AngularJS. Elle est très simple à mettre en oeuvre, pour utiliser avec des formulaires travaillant sur des données chargées en asynchrone, un besoin somme toute très fréquent.&lt;br /&gt;
&lt;br /&gt;
AngularJS fournit en standard le service &lt;b&gt;$resource&lt;/b&gt;, qui permet de gérer facilement des entités chargées en asynchrone, mais il est prévu pour fonctionner avec une API serveur de type REST. Il n&#39;est pas nécessaire que ce soit une API strictement RESTful, mais il ne faut pas qu&#39;elle s&#39;en éloigne trop pour que l&#39;utilisation du service $resource soit pertinente.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
L&#39;autre inconvénient, avec $resource, c&#39;est que les objets ressources contiennent uniquement les données, une fois que celles-ci sont arrivées. On n&#39;a pas accès à la promise du service $http utilisé en sous-main. Donc pas moyen par exemple d&#39;afficher un message “&lt;i&gt;Chargement en cours...&lt;/i&gt;” à la place d&#39;un formulaire tant que l&#39;objet ressource correspondant n&#39;est pas chargé. On ne peut pas non plus enchaîner des chargements de ressources, par exemple pour charger via plusieurs requêtes des entités liées entre elles, puisqu&#39;on ne peut pas déclencher les requêtes suivantes lorsque la première entité est alimentée.&lt;br /&gt;
&lt;br /&gt;
Si l&#39;on n&#39;utilise pas de $resource, il ne reste dans AngularJS pas d&#39;autre option que le service $http de base, qui renvoie une &lt;b&gt;promise&lt;/b&gt;. Avec des promises, les enchaînements de requêtes sont possibles. Mais à la fin de l&#39;article sur &lt;a href=&quot;http://www.frangular.com/2012/12/api-promise-angularjs.html&quot;&gt;l&#39;API Promise d&#39;AngularJS&lt;/a&gt;, j&#39;expliquais que si les promises s&#39;utilisent très bien dans les vues tant que c&#39;est pour faire de l&#39;affichage, ça pose problème dans les formulaires. En effet si les champs d&#39;un formulaire sont associés aux données d&#39;une promise, on retrouve les propriétés modifiées à un endroit et les propriétés non modifiées à un autre&amp;nbsp;(voir la fin de l&#39;article indiqué pour l&#39;explication détaillée). Ce n&#39;est pas vraiment pratique lors de la sauvegarde. Ce problème n&#39;existe pas avec une $resource, qu&#39;on peut utiliser sans aucun problème dans un formulaire.&lt;br /&gt;
&lt;br /&gt;
Alors comment faire pour avoir le côté pratique d&#39;une $resource qu&#39;on peut associer directement aux champs d&#39;un formulaire, mais sans les à-côtés inutiles quand on travaille avec un serveur qui n&#39;a manifestement jamais entendu parler de l&#39;architecture REST, et tout en gardant la possibilité d&#39;accéder à la promise de la requête $http ?&lt;br /&gt;
&lt;br /&gt;
Voici la solution à laquelle je suis arrivé, un service “&lt;b&gt;DeferredData&lt;/b&gt;” simple à utiliser, avec un &lt;a href=&quot;http://plnkr.co/edit/E5Nkv3?p=preview&quot; target=&quot;_blank&quot;&gt;exemple complet ici sur Plunker&lt;/a&gt;.
&lt;br /&gt;
&lt;br /&gt;
Le service est défini dans le fichier deferreddata.js :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;angular.module(&#39;deferreddata&#39;, [])
       .factory(&#39;DeferredData&#39;, [&#39;$q&#39;, function ($q) {

  function deferredDataBuilder(isArray) {
    var deferredData = isArray ? [] : {};
    var defer = $q.defer();
    var waiting = true;
    deferredData.$isWaiting = function() {
      return waiting;
    };
    deferredData.$getPromise = function() {
      return defer.promise;
    };
    deferredData.$resolve = function(data) {
      if (waiting) {
        waiting = false;
        // ... ici le code qui copie les données dans l&#39;objet deferredData...
      }
      return defer.resolve(deferredData);
    };
    return deferredData;
  }
    
  return {
    typeArray: function () {
      return deferredDataBuilder(true);
    },
    typeObject: function () {
      return deferredDataBuilder(false);
    }
  };
}]);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Pour créer un objet avec des données différées, il suffit alors d&#39;appeler &lt;i&gt;DeferredData.typeArray()&lt;/i&gt; si les données sont dans un tableau JavaScript, ou&lt;i&gt; DeferredData.typeObject() &lt;/i&gt;si elles sont dans les propriétés d&#39;un objet JS. L&#39;objet (ou le tableau) ainsi créé possède trois méthodes spéciales, en plus des données différées qu&#39;il va recevoir :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;$resolve(data)&lt;/b&gt; : pour charger l&#39;objet ou le tableau avec les données, quand elles sont arrivées&lt;/li&gt;
&lt;li&gt;&lt;b&gt;$getPromise()&lt;/b&gt; : qui renvoie la promise associée&lt;/li&gt;
&lt;li&gt;&lt;b&gt;$isWaiting()&lt;/b&gt; : qui indique si l&#39;objet ou le tableau est toujours en attente de ses données&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Dans l&#39;absolu la méthode &lt;i&gt;$isWaiting()&lt;/i&gt; n&#39;est pas indispensable, mais c&#39;est un raccourci plus pratique que d&#39;appeler à chaque fois &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;sur la promise pour faire basculer un témoin, surtout si on veut l&#39;utiliser dans une expression de la vue.&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
Voyons comment ce service est utilisé dans l&#39;exemple sur Plunker. Dans le fichier app.js, on a un service User, avec une méthode &lt;i&gt;load(id)&lt;/i&gt;&amp;nbsp;pour charger un utilisateur, et &lt;i&gt;save(user)&lt;/i&gt;&amp;nbsp;pour enregistrer l&#39;utilisateur modifié. L&#39;exemple est bidon car il n&#39;y a pas de serveur, en fait l&#39;utilisateur est un objet en dur renvoyé au bout de 3 secondes, pour simuler une requête suffisamment lente pour qu&#39;on puisse voir ce qui se passe, et l&#39;enregistrement affiche juste une alerte avec les données. Mais ça serait la même chose avec de vraies requêtes $http.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    load: function (id) {
      var deferredData = DeferredData.typeObject();
      $timeout(function () {
        deferredData.$resolve({firstname: &#39;Thierry&#39;, lastname: &#39;Chatel&#39;});
      }, 3000);
      return deferredData;
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Dans la fonction &lt;i&gt;load(id)&lt;/i&gt;&amp;nbsp;ci-dessus, on crée un objet de données différées en appelant DeferredData.typeObject(), et il est renvoyé immédiatement, à ce moment-là il ne contient aucune donnée. Son chargement est fait dans un $timeout au bout de 3000 millisecondes, en passant les données à charger à sa méthode &lt;i&gt;resolve()&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
Et dans le contrôleur, on publie cet objet dans le scope :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;app.controller(&#39;Ctrl&#39;, function($scope, User) {
  $scope.user = User.load(42);

  $scope.user.$getPromise().then(function (data) {
    $scope.message = &quot;User loaded&quot;;
  });
  
  $scope.save = function () {
    User.save($scope.user);
  };
});
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
L&#39;appel de la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;de sa promise (renvoyée par &lt;i&gt;$getPromise()&lt;/i&gt;) est un exemple de message publié dans le scope lorsque les données sont reçues.&lt;br /&gt;
&lt;br /&gt;
Dans la vue, les champs du formulaire sont associés directement aux propriétés de cet objet aux données différées :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    &amp;lt;h1&amp;gt;User: &amp;lt;span ng-show=&quot;user.$isWaiting()&quot;&amp;gt;[loading...]&amp;lt;/span&amp;gt;&amp;lt;/h1&amp;gt;
    &amp;lt;p class=&quot;message&quot;&amp;gt;{{message}}&amp;lt;/p&amp;gt;
    &amp;lt;form ng-hide=&quot;user.$isWaiting()&quot;&amp;gt;
      &amp;lt;p&amp;gt;
        Firstname:&amp;lt;br/&amp;gt;
        &amp;lt;input ng-model=&quot;user.firstname&quot;&amp;gt;
      &amp;lt;/p&amp;gt;
      
      &amp;lt;p&amp;gt;
        Lastname:&amp;lt;br/&amp;gt;
        &amp;lt;input ng-model=&quot;user.lastname&quot;&amp;gt;
      &amp;lt;/p&amp;gt;
      
      &amp;lt;p&amp;gt;
        &amp;lt;button ng-click=&quot;save()&quot;&amp;gt;Save&amp;lt;/button&amp;gt;
      &amp;lt;/p&amp;gt;
    &amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
J&#39;ai mis dans l&#39;élément &amp;lt;h1&amp;gt; un &amp;lt;span&amp;gt; qui n&#39;est visible que tant que les données ne sont pas arrivées. Et le formulaire au contraire est caché tant qu&#39;on n&#39;a pas reçu les données.&lt;br /&gt;
&lt;br /&gt;
C&#39;est très pratique à l&#39;usage, et bien sûr tout ce qui est saisi dans les champs du formulaire impacte directement les propriétés de l&#39;objet, donc il n&#39;y a pas de précaution particulière à prendre lors de la sauvegarde, on peut envoyer l&#39;objet tel quel au serveur.&lt;br /&gt;
&lt;br /&gt;
La seule chose qu&#39;il ne faut bien sûr pas faire, car ça reste quand même un objet dont les données sont différées, c&#39;est d&#39;utiliser la valeur d&#39;une de ses propriétés directement dans le code du contrôleur, pour la copier ailleurs, ou pour conditionner du code. Parce que lorsque le contrôleur est initialisé, les données ne sont pas encore là. Donc il ne faut jamais écrire quelque chose comme ça dans le contrôleur, parce qu&#39;on copierait une donnée encore vide :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$scope.userFirstname = $scope.user.firstname&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Mais par contre on peut le faire sans risque dans un callback passé à la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;de la promise :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$scope.user.$getPromise().then(function (data) {
  $scope.userFirstname = $scope.user.firstname;
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Voilà pour les explications sur ce petit exemple qui peut vous rendre de grand services en vous simplifiant la gestion de l&#39;asynchronisme. Bien sûr ce n&#39;est certainement pas la seule solution possible, aussi n&#39;hésitez pas à suggérer d&#39;autres approches dans les commentaires ou sur le forum.</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/6499642639613620165/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/02/solution-simple-formulaires-promise.html#comment-form' title='3 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/6499642639613620165'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/6499642639613620165'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/02/solution-simple-formulaires-promise.html' title='Solution simple pour des formulaires avec données différées'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-5979701328974789294</id><published>2013-02-04T15:02:00.000+01:00</published><updated>2013-02-04T15:02:58.666+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$scope"/><category scheme="http://www.blogger.com/atom/ns#" term="directive"/><category scheme="http://www.blogger.com/atom/ns#" term="drag and drop"/><category scheme="http://www.blogger.com/atom/ns#" term="évènement"/><title type='text'>Drag &amp; Drop avec AngularJS</title><content type='html'>Le drag &amp;amp; drop est revenu à la mode avec l&#39;arrivée des tablettes et des smartphones. Un temps boudé par les développeurs web, il est de bon ton d&#39;offrir de tels mécanismes dans nos applications web. A l&#39;image de &lt;a href=&quot;https://trello.com/&quot;&gt;Trello&lt;/a&gt; et de ses fameuses colonnes, nous allons voir comment rendre des éléments html &lt;i&gt;draggable &lt;/i&gt;et &lt;i&gt;droppable&lt;/i&gt;.
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;i&gt;Pour ceux qui veulent voir, directement, le code complet et une démo, un jsFiddle est disponible tout en bas.&lt;/i&gt;
&lt;br /&gt;
&lt;br /&gt;
AngularJS est très fort lorsqu&#39;il s&#39;agit d&#39;afficher un modèle. Mais qu&#39;en est-il de mettre à jour le modèle en fonction d&#39;actions dans la vue ? Et bien une fois de plus AngularJS nous surprend en nous permettant de rajouter des comportements au HTML. Pour ce faire il suffit de créer des directives.
&lt;br /&gt;
&lt;br /&gt;
Nous allons donc créer deux directives, une pour le drag et une autre pour le drop. La première directive se chargera de rendre l&#39;élément HTML draggable et gérera les événements dragStart et dragEnd. Tandis que la directive du drop permettra à l&#39;élément HTML d&#39;afficher un feedback lors du survol et appellera un callback lors du drop.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Ecouter un événement du DOM&lt;/h3&gt;
&lt;br /&gt;
Pour pouvoir placer un écouteur sur un événement d&#39;un élément HTML, AngularJS nous offre la fonction bind (tout droit issu de la petite partie de JQuery embarquée) :
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;
link: function(scope, element, attrs) {
    element.bind(&#39;dragenter&#39;, function(evt) {
        ...
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Un peu de code : les directives&lt;/h3&gt;
&lt;br /&gt;
Nous pouvons donc écire la directive drag :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;
app.directive(&quot;drag&quot;, [&quot;$rootScope&quot;, function($rootScope) {
  
  function dragStart(evt, element, dragStyle) {
    element.addClass(dragStyle);
    evt.dataTransfer.setData(&quot;id&quot;, evt.target.id);
    evt.dataTransfer.effectAllowed = &#39;move&#39;;
  };
  function dragEnd(evt, element, dragStyle) {
    element.removeClass(dragStyle);
  };
  
  return {
    restrict: &#39;A&#39;,
    link: function(scope, element, attrs)  {
      attrs.$set(&#39;draggable&#39;, &#39;true&#39;);
      scope.dragData = scope[attrs[&quot;drag&quot;]];
      scope.dragStyle = attrs[&quot;dragstyle&quot;];
      element.bind(&#39;dragstart&#39;, function(evt) {
        $rootScope.draggedElement = scope.dragData;
        dragStart(evt, element, scope.dragStyle);
      });
      element.bind(&#39;dragend&#39;, function(evt) {
        dragEnd(evt, element, scope.dragStyle);
      });
    }
  }
}]);
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Cette directive rajoute l&#39;attribut &quot;draggable&quot; sur l&#39;élément HTML et écoute les événements du DOM qui nous intéressent. Nous pouvons également remarquer que les attributs de la directive sont récupérés grâce au paramètre &lt;i&gt;attrs&lt;/i&gt; (attributs). Il est préférable, dans ce cas, de ne pas créer de scope isolé afin de pouvoir utiliser la directive de drag avec d&#39;autres directives (ng-repeat par exemple).
&lt;br /&gt;
&lt;br /&gt;
De plus, le $rootScope est utilisé pour stocker l&#39;objet (du modèle) qui sera transféré lors du drop. En effet, comme nous découplons la directive du drag et la directive du drop, lors du drop nous voudrons connaitre l&#39;objet du drag. Pour ce faire le plus simple est d&#39;utiliser le scope racine de tous les scopes. Mais vous pouvez très bien le stocker dans le scope parent si les directives sont sur des éléments HTML frères.
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgINPaVo6osa96jmtO8HbpVRHvhHalUksB9mvJRMhInLU16pjmnx6MwOjZOMzjowqBATGwAhXUDy7TYy0eDz8Idrap9hDBhyphenhyphensJnyWn43o-aV0X4y8yNg-SuqPO9wmE1GfSseknR9axm_rQ/s1600/rootScopeOuParentScope.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgINPaVo6osa96jmtO8HbpVRHvhHalUksB9mvJRMhInLU16pjmnx6MwOjZOMzjowqBATGwAhXUDy7TYy0eDz8Idrap9hDBhyphenhyphensJnyWn43o-aV0X4y8yNg-SuqPO9wmE1GfSseknR9axm_rQ/s400/rootScopeOuParentScope.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
La directive pour le drop :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;
app.directive(&quot;drop&quot;, [&#39;$rootScope&#39;, function($rootScope) {
  
  function dragEnter(evt, element, dropStyle) {
    evt.preventDefault();
    element.addClass(dropStyle);
  };
  function dragLeave(evt, element, dropStyle) {
    element.removeClass(dropStyle);
  };
  function dragOver(evt) {
    evt.preventDefault();
  };
  function drop(evt, element, dropStyle) {
    evt.preventDefault();
    element.removeClass(dropStyle);
  };
  
  return {
    restrict: &#39;A&#39;,
    link: function(scope, element, attrs)  {
      scope.dropData = scope[attrs[&quot;drop&quot;]];
      scope.dropStyle = attrs[&quot;dropstyle&quot;];
      element.bind(&#39;dragenter&#39;, function(evt) {
        dragEnter(evt, element, scope.dropStyle);
      });
      element.bind(&#39;dragleave&#39;, function(evt) {
        dragLeave(evt, element, scope.dropStyle);
      });
      element.bind(&#39;dragover&#39;, dragOver);
      element.bind(&#39;drop&#39;, function(evt) {
        drop(evt, element, scope.dropStyle);
        $rootScope.$broadcast(&#39;dropEvent&#39;, $rootScope.draggedElement, scope.dropData);
      });
    }
  }
}]);
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Outre la gestion des styles qui est triviale, nous remarquons le broadcast de l’événement &lt;i&gt;dropEvent&lt;/i&gt;, qui permet de prévenir les écouteurs qu&#39;un drop a eu lieu. Comme pour la directive de drag, il est plus simple d&#39;émettre l&#39;événement à partir du rootScope, mais il pourrait très bien être émit avec un scope parent.
&lt;br /&gt;
&lt;br /&gt;
Si vous n&#39;êtes pas à l&#39;aise avec les scopes et les événements, n&#39;hésitez pas à lire (ou à relire) l&#39;article : &lt;a href=&quot;http://www.frangular.com/2013/01/angularjs-scopes-et-evenements.html&quot;&gt;AngularJS : Scopes et évènements&lt;/a&gt;.
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Utilisons nos directives&lt;/h3&gt;
&lt;br /&gt;
Il ne nous reste plus qu&#39;à créer le HTML :&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;div ng-repeat=&quot;c in columns&quot; class=&quot;column&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; drag=&quot;c&quot; dragStyle=&quot;columnDrag&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; drop=&quot;c&quot; dropStyle=&quot;columnDrop&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {{c.title}}
&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Et notre contrôleur :&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;
app.controller(&#39;ColumnController&#39;, [&quot;$scope&quot;, &quot;$rootScope&quot;, 
  function($scope, $rootScope) {

    $scope.columns = [{title:&quot;1&quot;}, {title:&quot;2&quot;}, {title:&quot;3&quot;}, {title:&quot;4&quot;}];
    
    $rootScope.$on(&#39;dropEvent&#39;, function(evt, dragged, dropped) {
        var i, oldIndex1, oldIndex2;
        for(i=0; i&amp;lt;$scope.columns.length; i++) {
            var c = $scope.columns[i];
            if(dragged.title === c.title) {
                oldIndex1 = i;
            }
            if(dropped.title === c.title) {
                oldIndex2 = i;
            }
        }
        var temp = $scope.columns[oldIndex1];
        $scope.columns[oldIndex1] = $scope.columns[oldIndex2];
        $scope.columns[oldIndex2] = temp;
        $scope.$apply();
    });
    
}]);
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Dans ce contrôleur, nous écoutons l&#39;événement &lt;i&gt;dropEvent&lt;/i&gt; afin d&#39;inverser les colonnes dans le modèle. Ainsi, lors du &lt;i&gt;$scope.apply()&lt;/i&gt;, angularJs rafraîchira la vue, et le drag&amp;amp;drop sera effectif.
&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://jsfiddle.net/manland/DveUk/&quot;&gt;Le jsFiddle complet&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
En vrai ça donne quoi ?&lt;/h3&gt;
&lt;br /&gt;
Vous pouvez voir cette technique appliquée sur le projet &lt;a href=&quot;http://skimbo.studio-dev.fr/&quot;&gt;skimbo&lt;/a&gt;. La seule différence est qu&#39;il y a un écouteur supplémentaire, il s&#39;agit du service de communication avec le serveur. Ainsi le nouvel ordre des colonnes est envoyé au serveur pour être sauvegardé. Si ça vous intéresse vous trouverez tout le code du projet sur &lt;a href=&quot;https://github.com/Froggies/Skimbo&quot;&gt;github&lt;/a&gt;. &lt;br /&gt;
&lt;br /&gt;
J&#39;espère que cet article vous sera utile. N&#39;hésitez pas à commenter, améliorer et débattre dans les commentaires ou sur le forum !</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/5979701328974789294/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/02/drag-drop-avec-angularjs.html#comment-form' title='8 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/5979701328974789294'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/5979701328974789294'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/02/drag-drop-avec-angularjs.html' title='Drag &amp; Drop avec AngularJS'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/17650210088618641298</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgINPaVo6osa96jmtO8HbpVRHvhHalUksB9mvJRMhInLU16pjmnx6MwOjZOMzjowqBATGwAhXUDy7TYy0eDz8Idrap9hDBhyphenhyphensJnyWn43o-aV0X4y8yNg-SuqPO9wmE1GfSseknR9axm_rQ/s72-c/rootScopeOuParentScope.png" height="72" width="72"/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-3895328152029439440</id><published>2013-01-25T11:13:00.000+01:00</published><updated>2013-01-25T13:43:13.587+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="directive"/><category scheme="http://www.blogger.com/atom/ns#" term="graphique"/><title type='text'>Google Chart Tools avec AngularJS</title><content type='html'>Pouvoir inclure un graphique dans une application AngularJS par la simple utilisation d&#39;une directive, l&#39;idée est séduisante. Et bien sûr, pour respecter la philosophie du framework, le graphique doit se mettre à jour quand les données changent.&lt;br /&gt;
&lt;br /&gt;
&lt;iframe allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot; src=&quot;http://embed.plnkr.co/kGUhDM&quot; style=&quot;height: 580px; width: 100%;&quot;&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;br /&gt;
Voici un exemple de directive qui permet exactement ça, pour un graphique de type camembert, en l&#39;occurrence le&amp;nbsp;“Pie Chart” de &lt;i&gt;Google Chart Tools&lt;/i&gt;. L&#39;utilisation est toute simple :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;pie-chart data=&quot;chartData&quot; title=&quot;My Daily Activities&quot; 
           width=&quot;500&quot; height=&quot;350&quot;
           select=&quot;selectRow(selectedRowIndex)&quot;&amp;gt;&amp;lt;/pie-chart&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Dans les attributs, on fournit simplement les données (ici &quot;chartData&quot;, c&#39;est le nom d&#39;une propriété du scope, comme avec &lt;i&gt;ng-bind&lt;/i&gt;), le titre du graphique, ses dimensions, et éventuellement une expression qui est exécutée lorsque l&#39;utilisateur sélectionne une portion du camembert.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://plnkr.co/edit/PYC6m4?p=preview&quot; target=&quot;_blank&quot;&gt;Cliquez ici pour ouvrir ce premier exemple sur Plunker&lt;/a&gt;. Contrairement aux articles précédents, je n&#39;ai pas mis cet exemple sur jsFiddle, car celui-ci entrait en conflit avec les scripts de Google Chart Tools. Sur Plunker (qui d&#39;ailleurs est écrit avec AngularJS), il n&#39;y a pas de problème ; en local non plus bien sûr.&lt;br /&gt;
&lt;br /&gt;
Voyons d&#39;un peu plus près comment ça marche. Dans le fichier &lt;i&gt;app.js&lt;/i&gt;, on initialise AngularJS manuellement, en appelant &lt;i&gt;angular.bootstrap()&lt;/i&gt;, pour que l&#39;initialisation ait lieu seulement lorsque le chargement de la bibliothèque Google Chart Tools est terminé :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;google.setOnLoadCallback(function() {
  angular.bootstrap(document.body, [&#39;app&#39;]);
});
google.load(&#39;visualization&#39;, &#39;1&#39;, {packages: [&#39;corechart&#39;]});
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Le scope isolé de la directive&lt;/h2&gt;
Mais passons à la directive &lt;i&gt;pieChart&lt;/i&gt;&amp;nbsp;elle-même. Elle définit un scope isolé, contenant des propriétés qui correspondent aux cinq attributs possibles de l&#39;élément HTML :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    scope: {
      title:    &#39;@title&#39;,
      width:    &#39;@width&#39;,
      height:   &#39;@height&#39;,
      data:     &#39;=data&#39;,
      selectFn: &#39;&amp;amp;select&#39;
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
C&#39;est un exemple intéressant, car on y trouve les 3 façons de publier des propriétés dans le scope isolé d&#39;une directive en fonction des valeurs des attributs de l&#39;éléments HTML. Les propriétés &lt;i&gt;&lt;b&gt;title&lt;/b&gt;&lt;/i&gt;, &lt;b&gt;&lt;i&gt;width&lt;/i&gt;&amp;nbsp;&lt;/b&gt;et &lt;i&gt;&lt;b&gt;heigth&lt;/b&gt;&lt;/i&gt;, sont spécifiées par &#39;@&#39;, ce qui crée une propriété égale à la valeur de l&#39;attribut html associé (on peut d&#39;ailleurs omettre le nom de l&#39;attribut s&#39;il correspond au nom de la propriété). La valeur de cet attribut peut être une chaîne de caractères en dur, comme c&#39;est le cas dans ce premier exemple. Mais elle peut aussi être partiellement ou totalement évaluée par AngularJS si elle contient une expression encadrée par {{...}}, et dans ce cas la propriété est recalculée lorsque l&#39;expression incluse dans l&#39;attribut change de valeur. Vous l&#39;aurez deviné, c&#39;est en fait un binding automatique géré par AngularJS.&lt;br /&gt;
&lt;br /&gt;
Vous pouvez ouvrir &lt;a href=&quot;http://plnkr.co/edit/kGUhDM?p=preview&quot; target=&quot;_blank&quot;&gt;ici un second exemple sur Plunker&lt;/a&gt;, avec exactement la même directive, mais où les attributs &lt;i&gt;title&lt;/i&gt;, &lt;i&gt;width&lt;/i&gt;&amp;nbsp;et &lt;i&gt;height&lt;/i&gt;&amp;nbsp;sont alimentés par des propriétés du scope (pas le scope isolé de la directive, le scope parent, c&#39;est-à-dire le scope dans lequel est placé le graphique), ce qui permet d&#39;en changer la valeur dans la page web :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;pie-chart data=&quot;chartData&quot; title=&quot;{{chartTitle}}&quot; 
           width=&quot;{{chartWidth}}&quot; height=&quot;{{chartHeight}}&quot;
           select=&quot;selectRow(selectedRowIndex)&quot;&amp;gt;&amp;lt;/pie-chart&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
La propriété &lt;b&gt;&lt;i&gt;data&lt;/i&gt;&amp;nbsp;&lt;/b&gt;est publiée dans le scope isolé de la directive en la spécifiant avec un &#39;=&#39; suivi du nom de l&#39;attribut HTML, ce qui publie simplement la propriété du scope parent dont le nom est passé dans l&#39;attribut. Dans l&#39;exemple, avec &lt;i&gt;data=&quot;chartData&quot;&lt;/i&gt;, ça publie dans le scope isolé une propriété &lt;i&gt;data &lt;/i&gt;égale à la propriété &lt;i&gt;chartData&lt;/i&gt;&amp;nbsp;du scope parent. Donc c&#39;est un attribut qui s&#39;utilise sans doubles accolades, comme &lt;i&gt;ng-bind&lt;/i&gt;&amp;nbsp;ou &lt;i&gt;ng-model&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
La dernière propriété du scope isolé, &lt;i&gt;&lt;b&gt;selectFn&lt;/b&gt;&lt;/i&gt;, est spécifiée avec un &#39;&amp;amp;&#39; suivi du nom de l&#39;attribut. AngularJS publie comme propriété une fonction qui évalue l&#39;expression passée dans l&#39;attribut en question. Donc notre propriété &lt;i&gt;selectFn&lt;/i&gt;&amp;nbsp;sera une fonction générée par Angular qui évaluera l&#39;expression fournie, ou qui n&#39;évaluera rien si l&#39;attribut &lt;i&gt;select&lt;/i&gt;&amp;nbsp;n&#39;est pas présent. L&#39;attribut &lt;i&gt;select&lt;/i&gt;&amp;nbsp;peut ainsi contenir n&#39;importe quelle expression valide du langage d&#39;expression d&#39;AngularJS, comme dans un &lt;i&gt;ng-click&lt;/i&gt;. Ça peut être&amp;nbsp;un appel de fonction comme dans l&#39;exemple, ou encore l&#39;affectation d&#39;une propriété du scope parent du style &lt;i&gt;&quot;selectedRow = selectedRowIndex&quot;&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
Mais d&#39;où sort ce &lt;i&gt;selectedRowIndex&lt;/i&gt;&amp;nbsp;? Il est fournit lors de l&#39;exécution de cette fonction &lt;i&gt;selectFn&lt;/i&gt;&amp;nbsp;générée par AngularJS :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$scope.selectFn({selectedRowIndex: selectedItem.row});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Ça signifie que l&#39;attribut &lt;i&gt;select&lt;/i&gt;&amp;nbsp;peut contenir n&#39;importe quelle expression portant sur des propriétés ou fonctions du scope parent, et dans cette expression, &lt;i&gt;selectRowIndex&lt;/i&gt;&amp;nbsp;sera alimenté avec l&#39;index sélectionné.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
La fonction de dessin du graphique&lt;/h2&gt;
A la fin de la directive, on définit une fonction &lt;i&gt;draw()&lt;/i&gt;&amp;nbsp;chargée de dessiner le graphique, en lui fournissant les données, et des options comme le titre et les dimensions.&lt;br /&gt;
&lt;br /&gt;
Cette fonction est appelée une première fois systématiquement. Mais il y a aussi une série de quatre &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;qui la rappellent en cas de modification détectée par AngularJS sur les données du graphique, la valeur du titre, la largeur ou la hauteur. C&#39;est ce qui permet de voir le graphique se modifier si on change certaines données dans le premier exemple, ou également le titre ou les dimensions dans le second exemple.&lt;br /&gt;
&lt;br /&gt;
Le &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;correspondant aux données a une particularité, le troisième paramètre égal à &lt;i&gt;true &lt;/i&gt;dit à AngularJS de ne pas simplement surveiller la référence à l&#39;objet, mais toutes ses propriétés, c&#39;est un &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;en profondeur :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;      $scope.$watch(&#39;data&#39;, function() {
        draw();
      }, true); // true is for deep object equality checking
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Si l&#39;on écrivait juste une fonction dessinant le graphique, déclenchée sur ces quatre &lt;i&gt;$watch&lt;/i&gt;, ça marcherait bien sûr, mais ça ne serait pas optimal. Chaque &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;est surveillé par AngularJS en conservant l&#39;ancienne valeur, et en lui comparant la nouvelle. Mais la première fois, il n&#39;y a pas d&#39;ancienne valeur connue, et du coup chaque &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;est déclenché lors du premier &lt;i&gt;$digest&lt;/i&gt;&amp;nbsp;(la phase où AngularJS vérifie tous les &lt;i&gt;$watch&lt;/i&gt;). Du coup, on aurait systématiquement un premier dessin du graphique déclenché par l&#39;appel en dur de la fonction &lt;i&gt;draw()&lt;/i&gt;, immédiatement suivi de quatre autres déclenchés par les quatre &lt;i&gt;$watch&lt;/i&gt;. Dessiner cinq fois le même graphique alors que rien n&#39;a été modifié, ce n&#39;est pas très satisfaisant, même si ça va trop vite pour qu&#39;on puisse le voir.&lt;br /&gt;
&lt;br /&gt;
C&#39;est pour ça que j&#39;ai différé tout le contenu de la fonction &lt;i&gt;draw()&lt;/i&gt;&amp;nbsp;en utilisant le service &lt;i&gt;$timeout&lt;/i&gt;&amp;nbsp;pour qu&#39;il s&#39;exécute après la fin de la phase&amp;nbsp;&lt;i&gt;$digest&lt;/i&gt;, et surtout en le conditionnant sur une propriété &lt;i&gt;draw.triggered&lt;/i&gt;. Cette propriété est ajoutée à l&#39;objet JavaScript qui n&#39;est autre que la fonction &lt;i&gt;draw&lt;/i&gt;&amp;nbsp;elle-même, et elle est alimentée à &lt;i&gt;true&lt;/i&gt;&amp;nbsp;lorsque le traitement différé par &lt;i&gt;$timeout&lt;/i&gt;&amp;nbsp;a été déclenché. Du coup si cette propriété est déjà égale à &lt;i&gt;true&lt;/i&gt;, on évite de le déclencher une seconde fois, ce qui fait que le dessin proprement dit du graphique ne se fera qu&#39;une fois pour une même phase &lt;i&gt;$digest&lt;/i&gt;. Bien sûr on remet la propriété à &lt;i&gt;false&lt;/i&gt;&amp;nbsp;lorsque s&#39;effectue le traitement différé, pour en permettre à nouveau le déclenchement lors d&#39;une phase &lt;i&gt;$digest&lt;/i&gt;&amp;nbsp;ultérieure, si l&#39;une des valeurs surveillées par les &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;a été modifiée.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Gestion de la sélection sur le graphique&lt;/h2&gt;
L&#39;utilisateur de la page web peut sélectionner une des portions du camembert, et Google Chart Tools permet d&#39;enregistrer un &lt;i&gt;listener&lt;/i&gt;&amp;nbsp;qui sera déclenché lors de cette sélection :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;      // Chart selection handler
      google.visualization.events.addListener(chart, &#39;select&#39;, function () {
        var selectedItem = chart.getSelection()[0];
        if (selectedItem) {
          $scope.$apply(function () {
            $scope.selectFn({selectedRowIndex: selectedItem.row});
          });
        }
      });
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Ce que fait notre directive dans ce code, c&#39;est qu&#39;elle appelle la fameuse fonction &lt;i&gt;selectFn&lt;/i&gt;&amp;nbsp;générée dans le contexte isolé par AngularJS, et dont l&#39;exécution évalue l&#39;expression passée dans l&#39;attribut &lt;i&gt;select&lt;/i&gt;. C&#39;est ainsi que dans les deux exemples ça appelle la fonction&amp;nbsp;&lt;i&gt;selectRow(selectedRowIndex)&lt;/i&gt; publiée dans son scope par le contrôleur, où &lt;i&gt;selectedRowIndex&lt;/i&gt;&amp;nbsp;est alimenté par l&#39;index de la sélection. Visuellement ça met un fond rouge à la ligne correspondante dans le tableau des données, simplement en renvoyant une classe CSS différente pour la ligne sélectionnée via une fonction du scope.&lt;br /&gt;
&lt;br /&gt;
On pourrait de la même façon traiter d&#39;autres évènements susceptibles d&#39;être déclenchés par ce graphique en camembert, comme &lt;i&gt;onmouseover&lt;/i&gt;&amp;nbsp;et &lt;i&gt;onmouseout&lt;/i&gt;&amp;nbsp;qui correspondent au fait de passer la souris au-dessus des portions du camembert.&lt;br /&gt;
&lt;br /&gt;
Voilà pour cet exemple de directive intégrant un graphique de la bibliothèque Google Chart Tools à AngularJS, qui devient ainsi très pratique à utiliser. On peut bien sûr créer des directives similaires pour les autres graphiques de Google Chart Tools. Il y aura parfois de petites différences correspondant aux particularités de chaque graphique, par exemple le “&lt;i&gt;Line Chart&lt;/i&gt;” prend plusieurs séries de valeurs, et la sélection ne correspond plus simplement à un index, mais à un index à l&#39;intérieur d&#39;une série.&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/3895328152029439440/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/01/google-chart-tools-avec-angularjs.html#comment-form' title='2 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/3895328152029439440'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/3895328152029439440'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/01/google-chart-tools-avec-angularjs.html' title='Google Chart Tools avec AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-494068535174553690</id><published>2013-01-22T15:10:00.000+01:00</published><updated>2013-02-02T14:51:42.551+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="architecture"/><title type='text'>De JSF à AngularJS</title><content type='html'>&lt;br /&gt;
Ce matin j’ai partagé sur Twitter &lt;a href=&quot;http://statelessprime.blogspot.be/2013/01/comparison-between-angularjs-and-jsf.html&quot; target=&quot;_blank&quot;&gt;cet article en anglais&lt;/a&gt;&amp;nbsp;qui fait une comparaison entre AngularJS et JSF. D’autres que moi l’ont partagé aussi, mais il y a eu quelques réactions étonnées, du genre : “&lt;i&gt;c’est une blague ?&lt;/i&gt;”.&lt;br /&gt;
&lt;br /&gt;
Pour moi la comparaison n’a rien d’absurde, et mérite de plus amples explications que ce qu’on peut écrire dans un tweet de 140 caractères.&lt;br /&gt;
&lt;br /&gt;
Tout comme l’auteur de l’article, je viens du monde Java et j’ai travaillé avec JSF pendant 5 ans, certes surtout comme architecte et en support à mes collègues qui l’utilisaient au quotidien, mais j’ai aussi fait ma part de développements. Et moi aussi en découvrant AngularJS, j’ai forcément constaté des ressemblances entre les deux frameworks.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h2&gt;
Des templates déclaratifs avec data-binding...&lt;/h2&gt;
Tout d’abord, quand je parle de JSF, ce n’était pas le JSF des origines avec des templates en JSP et des tonnes de fichiers de configuration en XML, une horreur que je n’aurais jamais accepté d’utiliser. Il s’agissait d’une combinaison de JSF, de Facelets pour les templates (qui est la technologie de templates utilisée dans JSF 2.0), et du framework Seam (devenu Weld) pour l’injection de dépendances des services, et leur publication dans les différents contextes afin qu’ils soient directement utilisables pour le data-binding des composants JSF. N’allez pas raconter que j’ai dit qu’AngularJS ressemblait aux immondes pages JSP, je démentirai formellement !&lt;br /&gt;
&lt;br /&gt;
Avec cette combinaison JSF + Facelets + Seam (ou Spring si vous voulez), on a des templates déclaratifs basés sur HTML avec du data-binding et un langage d&#39;expression, qui ressemblent beaucoup à ceux d’AngularJS comme l&#39;a très bien montré l&#39;auteur de l&#39;article cité. Je suis un grand partisan des templates déclaratifs. Je trouve l’approche déclarative beaucoup plus efficace que l’approche procédurale pour la création d’une interface utilisateur. Avant JSF, j’ai fait plusieurs années de Swing, et j’avais pu constater à quel point c’était lourd ; puissant certes, mais avec une lourdeur toujours présente pour une puissance rarement nécessaire. Quand on doit écrire un éditeur “rich text”, on apprécie la puissance de Swing, mais pour des usages plus classiques on passe son temps à maudire sa lourdeur. JavaFX était une très bonne idée, celle de rajouter des templates déclaratifs à la puissance de Swing, dommage qu’elle soit arrivée trop tard.&lt;br /&gt;
&lt;br /&gt;
Mais revenons-en à nos templates JSF/Facelets. Aussi similaires soient-ils, il y a quand même des différences avec ceux d’AngularJS. Déjà, les composants JSF sont forcéments des éléments du pseudo XHTML, alors que les directives d’AngularJS peuvent être aussi des attributs. Du coup avec AngularJS, on peut avoir plusieurs directives sur un même élément : une directive est généralement un comportement applicable à un élément plutôt que l’élément lui-même. Avec Facelets, il faut imbriquer plusieurs composants JSF pour combiner leurs comportements.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
... mais traités côté serveur&lt;/h2&gt;
Une différence bien plus fondamentale, c’est que le template AngularJS est du HTML qui est analysé par le navigateur web avant qu’AngularJS n’intervienne dessus, tandis qu’avec JSF, c’est le moteur de Facelets qui convertit &lt;b&gt;côté serveur&lt;/b&gt; le template en HTML pur. Avantage pour JSF, comme le traitement est fait côté serveur, on a un accès direct aux objets Java du serveur, notamment aux entités persistantes. Il n’y a pas besoin d’une couche intermédiaire de web services pour échanger les données avec le client, c’est nettement plus simple, surtout lorsqu’il y a une gestion assez pointue des droits sur les données de ces objets. Par contre AngularJS est plus proche du HTML, travaille en manipulant le DOM dans le navigateur web plutôt qu’en générant du HTML souvent trop verbeux comme le font les composants JSF. On aura d’une façon générale avec AngularJS des pages HTML plus propres et moins lourdes.&lt;br /&gt;
&lt;br /&gt;
L’autre gros problème de JSF, c’est le stockage de l’état des composants, qui peut se faire côté serveur ou côté client. Encore qu’il est difficile d’envisager sérieusement le stockage de l’état côté serveur : comme JSF ne sait pas vraiment ce qu’il se passe dans le navigateur, il faut conserver l’état de plein de pages pour espérer qu’un utilisateur ayant ouvert plusieurs onglets ne soumette pas un formulaire d’une page dont l’état a été perdu ; inutile de préciser que dans ce mode la consommation de mémoire par utilisateur connecté est tout simplement colossale. Le mode qui fonctionne mieux, c’est celui consistant à stocker l’état des composants JSF sérialisé dans un champ caché de la page web. Plus de problème de consommation de mémoire côté serveur, ni de pages dont l’état a été perdu puisqu’il est conservé dans la page elle-même, mais ça implique de gros échanges pour passer dans un sens et dans l’autre cet état sérialisé, pour la moindre requête Ajax. Alors on en vient à faire un peu de jQuery pour éviter ces lourds échanges serveurs dès qu’on veut mettre un peu d’interaction dans une page web. Le résultat, c&#39;est qu&#39;on mélange du code client dans le template serveur, et ce qui était plutôt élégant devient horrible à maintenir.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Du web “terminal passif” au web “client-serveur”&lt;/h2&gt;
C’est là qu’on touche aux limites du fait de gérer côté serveur une interface client. JSF, du moins une combinaison JSF + Facelets + Seam, est très efficace pour créer des applications web classiques, pour ne pas dire à l’ancienne, avec des pages web distinctes et peu d’Ajax. C’est le web façon &lt;i&gt;terminal passif&lt;/i&gt;. Pour ce genre d’applications, gérer l’interface utilisateur côté client, même avec un framework aussi pratique qu’AngularJS, suppose de créer toute une API en web services REST échangeant du JSON, ce dont on est dispensé avec JSF.&lt;br /&gt;
&lt;br /&gt;
Mais dès qu’on veut faire une application web plus réactive, éventuellement mono-page, c’est-à-dire quand on veut passer du web &lt;i&gt;terminal passif&lt;/i&gt; au web &lt;i&gt;client-serveur&lt;/i&gt;, alors JSF n’est plus du tout pertinent, et AngularJS s’avère un bien meilleur choix. JavaScript est de nos jours largement assez puissant pour gérer dans le navigateur web toute la couche de présentation d’une application, et c’est une approche beaucoup plus logique et plus cohérence, pour avoir de l’interaction sans échange avec le serveur, pour gérer correctement l’historique du navigateur dans une application mono-page.. En bref, on peut maintenant abandonner les solutions trop complexes et d’un autre âge, qui découlaient de la nécessité de gérer la présentation dans une technologie complètement déconnectée du langage natif du navigateur web, et même souvent exécutée côté serveur. Pour moi les JSF, GWT ou autre Vaadin apparaissent aujourd&#39;hui comme des impasses de l’évolution du développement web, des espèces condamnées à l’extinction à plus ou moins court terme.&lt;br /&gt;
&lt;br /&gt;
Et si un client JavaScript suppose une API en web services pour l’échange des données, c’est de toute façon une architecture qui se généralise car elle permet aussi de communiquer avec des applications mobiles, ou des services externes. Donc oui, on peut encore trouver des cas où une application web pourra être développée plus rapidement avec JSF qu’avec AngularJS, si ce n’est pas une application mono-page, qu’elle comporte peu d’interactions côté client, qu’il n’y aura pas de version mobile, etc. Ça n’empêche pas que malgré leurs similitudes, l’approche de JSF est du passé, et c’est celle d’AngularJS qui prévaudra pour ces prochaines années. Les ressemblances permettront une transition plus facile pour les anciens de JSF, et c&#39;est une bonne chose.</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/494068535174553690/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/01/jsf-angularjs.html#comment-form' title='18 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/494068535174553690'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/494068535174553690'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/01/jsf-angularjs.html' title='De JSF à AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>18</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-7933592716032287451</id><published>2013-01-15T08:00:00.000+01:00</published><updated>2013-01-15T11:08:03.729+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="clavier"/><category scheme="http://www.blogger.com/atom/ns#" term="contrôleur"/><category scheme="http://www.blogger.com/atom/ns#" term="service"/><category scheme="http://www.blogger.com/atom/ns#" term="évènement"/><title type='text'>AngularJS: Service de gestion de raccourcis clavier</title><content type='html'>&lt;style type=&quot;text/css&quot;&gt;
.message{background-size: 40px 40px;background-image: linear-gradient(135deg, rgba(255, 255, 255, .05) 25%, transparent 25%,transparent 50%, rgba(255, 255, 255, .05) 50%, rgba(255, 255, 255, .05) 75%,transparent 75%, transparent);box-shadow: inset 0 -1px 0 rgba(255,255,255,.4);width: 95%;border: 1px solid;color: #fff;padding: 15px;}
.info{background-color: #4ea5cd;border-color: #3b8eb5;}
.error{background-color: #de4343;border-color: #c43d3d;}
.warning{background-color: #eaaf51;border-color: #d99a36;}
.success{background-color: #61b832;border-color: #55a12c;}
.message h3{margin: 0 0 5px 0; }
&lt;/style&gt;
A l&#39;image des applications Google, Twitter et autres grosses applications Web du moment, il est très &quot;user-friendly&quot; (convivial pour les franglophobes !) de mettre en place une série de raccourcis clavier pour utiliser de manière optimisée l&#39;application.&lt;br /&gt;
&lt;br /&gt;
Les raccourcis clavier sont une touche ou une combinaison de deux ou plusieurs touches à presser simultanément afin d&#39;éviter une action de pointage par la souris. Le fait de ne pas déplacer en permanence la main entre le clavier et la souris fait déjà gagner un temps précieux pour les gens qui travaillent toute la journée sur une application. De plus, à chaque action de la souris, il faut un temps de pointage pour déplacer le curseur sur l&#39;endroit voulu.&lt;br /&gt;
&lt;br /&gt;
Comment centraliser la gestion de ces raccourcis dans une application AngularJS ?&lt;br /&gt;
&lt;br /&gt;
Nous allons voir comment utiliser un service AngularJS pour rendre cette gestion de raccourcis clavier générale.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;h2&gt;
Les services dans AngularJS&lt;/h2&gt;
&lt;br /&gt;
Je vous invite à prendre connaissance de cet article concernant la création et la déclaration des services dans AngularJS:&amp;nbsp;&lt;a href=&quot;http://www.frangular.com/2012/12/differentes-facons-de-creer-un-service-angularjs.html&quot;&gt;http://www.frangular.com/2012/12/differentes-facons-de-creer-un-service-angularjs.html&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Cet article vous décrira ce qu&#39;est un service pour AngularJS, et vous expliquera comment les créer, quel pattern utiliser, etc... Bonne lecture!&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Création du service&lt;/h2&gt;
&lt;br /&gt;
Pour créer ce service, je me suis inspiré très fortement d&#39;une librairie disponible sous licence BSD (cf. &lt;a href=&quot;http://fr.wikipedia.org/wiki/Licence_BSD&quot; target=&quot;_blank&quot;&gt;définition&lt;/a&gt;) disponible à cette adresse:&amp;nbsp;&lt;a href=&quot;http://www.openjs.com/scripts/events/keyboard_shortcuts/&quot;&gt;http://www.openjs.com/scripts/events/keyboard_shortcuts/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
J&#39;ai effectué la conversion de la librairie pour qu&#39;elle s&#39;intègre correctement avec l&#39;environnement que nous met à disposition AngularJS.&lt;br /&gt;
&lt;br /&gt;
Vous trouverez ci-dessous un &lt;a href=&quot;http://jsfiddle.net/firehist/nzUBg/&quot; target=&quot;_blank&quot;&gt;code de démonstration&lt;/a&gt; sous jsFiddle.&lt;br /&gt;
&lt;br /&gt;
&lt;iframe allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot; src=&quot;http://jsfiddle.net/firehist/nzUBg/embedded/&quot; style=&quot;height: 300px; width: 100%;&quot;&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;br /&gt;
Voilà un cas simple d&#39;utilisation des services avec AngularJS. La force de ces singletons, c&#39;est qu&#39;ils assurent une unicité très forte dans toute notre application. Cette assurance nous permet de gérer les raccourcis facilement de n&#39;importe quel contrôleur ou service de notre application, à la seule condition d&#39;avoir injecté correctement ce service.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;warning message&quot;&gt;
&lt;h3&gt;
Intégrer les modifications dans le cycle de vie d&#39;AngularJS&lt;/h3&gt;
Lors de l&#39;utilisation d&#39;un raccourci, le callback est exécuté en utilisant le service $timeout fourni par AngularJS, ce qui permet de placer son exécution au sein du cycle de vie d&#39;AngularJS.&lt;br /&gt;
Il est également possible d&#39;utiliser directement la méthode $apply ou $digest sur le $rootScope (cf &lt;a href=&quot;http://www.frangular.com/2013/01/angularjs-scopes-et-evenements.html&quot; target=&quot;_blank&quot;&gt;article sur les Scopes et évènements&lt;/a&gt;), mais ayant eu quelques fois l&#39;erreur suivante : &lt;strong&gt;Error: $digest already in progress&lt;/strong&gt;, j&#39;ai décidé d&#39;opter pour le $timeout qui s&#39;occupe seul de synchroniser les données tout en évitant les erreurs.&lt;br /&gt;
PS: Il existe un moyen de prévenir cette erreur à cette adresse &lt;a href=&quot;https://coderwall.com/p/ngisma&quot;&gt;https://coderwall.com/p/ngisma&lt;/a&gt;, mais je trouve personnellement cette implémentation plus lourde qu&#39;un bon $timeout!
&lt;br /&gt;
J&#39;attends vos avis sur la question.
&lt;/div&gt;
&lt;br /&gt;
&lt;h2&gt;
Aller plus loin&lt;/h2&gt;
&lt;br /&gt;
Il est bien évidemment possible d&#39;améliorer cette librairie en permettant d&#39;effectuer une suite de combinaisons de touches (à la façon de la librairie jQuery &lt;a href=&quot;http://craig.is/killing/mice&quot; target=&quot;_blank&quot;&gt;MouseTrap&lt;/a&gt;).&lt;br /&gt;
&lt;br /&gt;
On peut également penser à lever des évènements afin de rendre le service totalement autonome. Je vous conseille de lire un article de FrAngular qui vous aidera à bien appréhender le fonctionnement des évènements dans AngularJS.&lt;br /&gt;
&lt;br /&gt;
L&#39;idée de cette amélioration est de rendre indépendant ce service. Toutes les combinaisons (ou suites de combinaisons si vous en avez le temps :D) seraient alors initialisées dans une méthode &lt;i&gt;init()&lt;/i&gt;&amp;nbsp;de notre service.&lt;br /&gt;
&lt;br /&gt;
Chaque raccourci clavier initialisé se verrait attribuer une étiquette unique qui sera diffusée à travers tous les scopes de notre application grâce à la méthode $broadcast sur le service AngularJS $rootScope (cf article sur les évènements).&lt;br /&gt;
&lt;br /&gt;
On est maintenant libre de prendre en compte ou non ces raccourcis dans le contrôleur, et d&#39;effectuer les actions appropriées au contrôleur courant.&lt;br /&gt;
&lt;br /&gt;
Merci de votre lecture. N&#39;hésitez pas à nous faire part de vos remarques ou interrogations si vous en avez... :-)&lt;br /&gt;
&lt;br /&gt;
Bon dév&#39;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/7933592716032287451/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/01/angularjs-service-raccourcis-clavier.html#comment-form' title='3 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/7933592716032287451'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/7933592716032287451'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/01/angularjs-service-raccourcis-clavier.html' title='AngularJS: Service de gestion de raccourcis clavier'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/01293399064543705328</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total><georss:featurename>34090 Montpellier, France</georss:featurename><georss:point>43.6348676 3.8604401999999709</georss:point><georss:box>43.5888971 3.7797591999999707 43.6808381 3.9411211999999711</georss:box></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-8692483714621350039</id><published>2013-01-11T11:21:00.000+01:00</published><updated>2013-01-12T10:56:46.340+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ng-repeat"/><title type='text'>Tableaux complexes avec AngularJS : ng-repeat et tbody</title><content type='html'>&lt;span style=&quot;color: #990000;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;b&gt;[Ajout du 12/01/2013]&lt;/b&gt;&lt;/span&gt; &lt;/span&gt;&lt;span style=&quot;color: #660000;&quot;&gt;J&#39;ai modifié la solution proposée à la fin de l&#39;article pour utiliser une directive &lt;i&gt;ng-switch&lt;/i&gt; plutôt que &lt;i&gt;ng-show&lt;/i&gt;, ce qui est beaucoup plus pertinent.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
On m&#39;a demandé plusieurs fois ce qui peut être compliqué à faire avec AngularJS. Il y a une chose qui parfois s&#39;avère contraignante, surtout pour présenter des tableaux complexes, c&#39;est le fait que les templates d&#39;AngularJS doivent être du HTML, sinon forcément valide, du moins compris par le navigateur.&lt;br /&gt;
&lt;br /&gt;
AngularJS compile le template à partir du DOM chargé par le navigateur. Il ne travaille pas sur le fichier source HTML, mais sur le DOM construit par le navigateur à partir du source, et ça fait une grosse différence. Ce fonctionnement est un des principaux atouts d&#39;AngularJS, mais dans quelques rares cas, il peut s&#39;avérer contraignant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
C&#39;est le cas avec les tableaux HTML. Quand on a un tableau simple, avec un modèle de ligne répété en fonction des éléments d&#39;une liste, il n&#39;y a pas de difficulté. Mais imaginons un tableau un peu plus compliqué comme celui-ci, qui présente les navigateurs web utilisés par les visiteurs sur les premières semaines d&#39;existence de FrAngular :&lt;br /&gt;
&lt;style type=&quot;text/css&quot;&gt;
#tableBrowsers td,th {
  margin: 0px; 
  padding: 0.3em;
}
&lt;/style&gt;

&lt;br /&gt;
&lt;table id=&quot;tableBrowsers&quot; style=&quot;border-collapse: collapse; border-spacing: 0px; color: black; font-size: 0.9em; margin: 1em;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
 &lt;th style=&quot;background-color: #ccddff; text-align: left;&quot;&gt;Browser&lt;/th&gt;
 &lt;th style=&quot;background-color: #ccddff; text-align: right;&quot;&gt;%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
 &lt;td style=&quot;background-color: #eeeeff;&quot;&gt;Chrome&lt;/td&gt;
 &lt;td style=&quot;background-color: #eeeeff; text-align: right;&quot;&gt;68.83 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
 &lt;td style=&quot;font-size: 0.7em;&quot;&gt;Chrome 23.0.1271.97&lt;/td&gt;
 &lt;td style=&quot;font-size: 0.7em; text-align: right;&quot;&gt;46.68 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
 &lt;td style=&quot;font-size: 0.7em;&quot;&gt;Chrome 18.0.1025.166&lt;/td&gt;
 &lt;td style=&quot;font-size: 0.7em; text-align: right;&quot;&gt;10.97 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
 &lt;td style=&quot;font-size: 0.7em;&quot;&gt;Chrome 23.0.1271.101&lt;/td&gt;
 &lt;td style=&quot;font-size: 0.7em; text-align: right;&quot;&gt;10.66 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;

&lt;tbody&gt;
&lt;tr&gt;
 &lt;td style=&quot;background-color: #eeeeff;&quot;&gt;Firefox&lt;/td&gt;
 &lt;td style=&quot;background-color: #eeeeff; text-align: right;&quot;&gt;17.87 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
 &lt;td style=&quot;font-size: 0.7em;&quot;&gt;Firefox 17.0&lt;/td&gt;
 &lt;td style=&quot;font-size: 0.7em; text-align: right;&quot;&gt;70.24 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
 &lt;td style=&quot;font-size: 0.7em;&quot;&gt;Firefox 16.0&lt;/td&gt;
 &lt;td style=&quot;font-size: 0.7em; text-align: right;&quot;&gt;12.50 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;

&lt;tbody&gt;
&lt;tr&gt;
 &lt;td style=&quot;background-color: #eeeeff;&quot;&gt;Safari&lt;/td&gt;
 &lt;td style=&quot;background-color: #eeeeff; text-align: right;&quot;&gt;4.36 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
 &lt;td style=&quot;font-size: 0.7em;&quot;&gt;Safari 536.26.17&lt;/td&gt;
 &lt;td style=&quot;font-size: 0.7em; text-align: right;&quot;&gt;36.59 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
 &lt;td style=&quot;font-size: 0.7em;&quot;&gt;Safari 8536.25&lt;/td&gt;
 &lt;td style=&quot;font-size: 0.7em; text-align: right;&quot;&gt;36.59 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
 &lt;td style=&quot;font-size: 0.7em;&quot;&gt;Safari 7534.48.3&lt;/td&gt;
 &lt;td style=&quot;font-size: 0.7em; text-align: right;&quot;&gt;19.51 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;br /&gt;
On a dans ce tableau deux niveaux de répétition :&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;un premier niveau sur le navigateur : Chrome, Firefox, Safari &lt;i&gt;(je n&#39;ai mis que les premiers de la liste, et IE était à 0.34%)&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;un second niveau sur les versions de ces navigateurs&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
La seconde répétition, sur la version, porte sur une seule ligne du tableau, il suffit de mettre un attribut&amp;nbsp;&lt;b&gt;ng-repeat&lt;/b&gt; sur le &amp;lt;tr&amp;gt; servant de template. Mais la première répétition, sur le navigateur, doit porter sur deux &amp;lt;tr&amp;gt; : le template de ligne concernant le navigateur, et le template de ligne indiquant la version qui est lui-même répété.&lt;br /&gt;
&lt;br /&gt;
On pourrait avoir même avec un seul niveau de répétition des cas où l&#39;on veut répéter plusieurs lignes ensemble. L&#39;attribut&amp;nbsp;&lt;b&gt;ng-repeat&lt;/b&gt; doit être positionné sur un élément HTML, donc impossible de le mettre sur un &amp;lt;tr&amp;gt; dans ce cas, car une seule ligne serait répétée. On ne peut pas non plus intercaler un &amp;lt;div&amp;gt; dans la balise &amp;lt;table&amp;gt; pour regrouper les deux &amp;lt;tr&amp;gt;, ce n&#39;est pas valide en HTML, et un tel &amp;lt;div&amp;gt; intermédiaire est ignoré par les navigateurs, ce qui fait qu&#39;il n&#39;existe pas dans le DOM du template que compile AngularJS. C&#39;est là qu&#39;on voit la différence entre un traitement fait sur le source du template, et un traitement fait sur le DOM que le navigateur a construit à partir du code source.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
&amp;lt;tbody&amp;gt; à la rescousse&lt;/h2&gt;
Heureusement il existe une balise HTML permettant de regrouper des &amp;lt;tr&amp;gt;, c&#39;est la balise &amp;lt;tbody&amp;gt;, et par chance on a le droit de mettre plusieurs &amp;lt;tbody&amp;gt; dans une même table. Du coup c&#39;est ce qu&#39;on va utiliser avec AngularJS pour répéter plusieurs &amp;lt;tr&amp;gt; ensemble : on les regroupe dans un &amp;lt;tbody&amp;gt;, et on met l&#39;attribut &amp;nbsp;&lt;b&gt;ng-repeat&lt;/b&gt; sur le &amp;lt;tbody&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Vous avez &lt;a href=&quot;http://jsfiddle.net/tchatel/tQ5jb/&quot; target=&quot;_blank&quot;&gt;un exemple ici sur jsFiddle&lt;/a&gt;, qui construit ce tableau des navigateurs, avec un premier niveau de répétition sur le &amp;lt;tbody&amp;gt;, et un second niveau de répétition sur un &amp;lt;tr&amp;gt; à l&#39;intérieur de ce &amp;lt;tbody&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;tbody ng-repeat=&quot;browser in browsers&quot;&amp;gt;
  &amp;lt;tr class=&quot;total&quot;&amp;gt;
    &amp;lt;td class=&quot;browser&quot;&amp;gt;{{browser.name}}&amp;lt;/td&amp;gt;
    &amp;lt;td class=&quot;pct&quot;&amp;gt;{{browser.total}} %&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
  &amp;lt;tr ng-repeat=&quot;version in browser.versions&quot; class=&quot;detail&quot;&amp;gt;
    &amp;lt;td class=&quot;browser&quot;&amp;gt;{{browser.name}} {{version.name}}&amp;lt;/td&amp;gt;
    &amp;lt;td class=&quot;pct&quot;&amp;gt;{{version.value}} %&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;tbody&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Cette solution simple fonctionne très bien si l&#39;on a un seul niveau de répétition. Elle convient encore avec deux niveaux de répétition, à condition que le second niveau porte sur une seule ligne. Pour les cas rarissimes avec trois niveaux de répétitions ou plus, ou seulement deux niveaux mais où il faut répéter plusieurs lignes au second niveau, ça ne convient plus. Il n&#39;est pas possible d&#39;imbriquer des balises &amp;lt;tbody&amp;gt;, c&#39;est interdit en HTML et ignoré par le navigateur, et donc un second &amp;lt;tbody&amp;gt; imbriqué dans notre fichier source n&#39;existerait pas dans le DOM compilé par AngularJS.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Transformer les données&lt;/h2&gt;
Il y a une solution plus générique, qui fonctionne dans tous les cas. C&#39;est de transformer les données, pour en faire une liste correspondant exactement au tableau à afficher, avec un élément pour chaque &amp;lt;tr&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
En général on fait le data-binding d&#39;une vue sur les données telles qu&#39;elles ont été reçues du serveur. Mais dans des cas complexes où la structure des données n&#39;est pas bien adaptée, il peut être plus pratique de transformer ces données dans le code du contrôleur (ou dans une fonction du scope si ce sont des données qui peuvent être modifiées), pour en faire un modèle plus simple à afficher.&lt;br /&gt;
&lt;br /&gt;
Dans notre cas d&#39;un tableau particulièrement exotique, il suffit de mettre à plat les différents niveaux de répétition, pour n&#39;en avoir plus qu&#39;un seul correspondant aux &amp;lt;tr&amp;gt; de la table HTML. La répétition se fait sur un unique &amp;lt;tbody&amp;gt; contenant plusieurs templates de &amp;lt;tr&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #990000;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;b&gt;[Ajout du 12/01/2013]&lt;/b&gt;&lt;/span&gt; &lt;/span&gt;&lt;span style=&quot;color: #660000;&quot;&gt;J&#39;avais proposé initialement d&#39;utiliser des attributs &lt;b&gt;ng-show&lt;/b&gt; pour conditionner l&#39;affichage du template adéquat pour chaque occurrence (&lt;a href=&quot;http://jsfiddle.net/tchatel/9vPKm/&quot; target=&quot;_blank&quot;&gt;voir le jsFiddle ici&lt;/a&gt;).&amp;nbsp;Mais l&#39;attribut &lt;i&gt;ng-show&lt;/i&gt;, tout comme son pendant &lt;i&gt;ng-hide&lt;/i&gt;, masque simplement l&#39;élément s&#39;il ne doit pas être visible, via une propriété css &lt;i&gt;display: none&lt;/i&gt;. L&#39;élément est tout de même présent, même s&#39;il est caché, ce qui signifie que chaque occurrence de &amp;lt;tbody&amp;gt; contient tous les templates de &amp;lt;tr&amp;gt;. Merci à ceux qui ont fait remarquer ce problème dans les commentaires. Si ça n&#39;est pas prohibitif pour un petit tableau, avec un gros volume de données ça peut être un vrai problème, car du coup le DOM HTML est plus volumineux, et surtout ça multiplie le nombre de bindings à vérifier pour AngularJS.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;color: #660000;&quot;&gt;&lt;br /&gt;
Je pensais qu&#39;il n&#39;y avait pas de directive standard dans AngularJS permettant d&#39;avoir réellement qu&#39;un &amp;lt;tr&amp;gt; présent. Mais la nuit porte conseil, et ça m&#39;a conduit à une solution bien meilleure, à laquelle je n&#39;avais pas pensé sur le moment : il suffit d&#39;utiliser les directives &lt;b&gt;ng-switch&lt;/b&gt; et &lt;b&gt;ng-switch-when&lt;/b&gt;, qui finalement sont faites exactement pour ça.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;color: #660000;&quot;&gt;&lt;br /&gt;
Dont voici la bonne solution (&lt;a href=&quot;http://jsfiddle.net/tchatel/hcTz4/&quot; target=&quot;_blank&quot;&gt;jsFiddle ici&lt;/a&gt;), où les templates de &amp;lt;tr&amp;gt; sont conditionnés selon la valeur de la propriété &lt;i&gt;line.type&lt;/i&gt;. Avec un &lt;i&gt;ng-switch&lt;/i&gt; sur l&#39;élément &amp;lt;tbody&amp;gt; et un &lt;i&gt;ng-switch-when&lt;/i&gt; sur chacun des &amp;lt;tr&amp;gt;, on obtient bien le résultat voulu, et sans &amp;lt;tr&amp;gt; cachés. Seul le &amp;lt;tr&amp;gt; correspondant à la valeur du &lt;i&gt;ng-switch-when&lt;/i&gt; est présent dans chaque &amp;lt;tbody&amp;gt; :&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;tbody ng-repeat=&quot;line in lines&quot; ng-switch=&quot;line.type&quot;&amp;gt;
  &amp;lt;tr ng-switch-when=&quot;total&quot; class=&quot;total&quot;&amp;gt;
    &amp;lt;td class=&quot;browser&quot;&amp;gt;{{line.name}}&amp;lt;/td&amp;gt;
    &amp;lt;td class=&quot;pct&quot;&amp;gt;{{line.value}} %&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
  &amp;lt;tr ng-switch-when=&quot;detail&quot; class=&quot;detail&quot;&amp;gt;
    &amp;lt;td class=&quot;browser&quot;&amp;gt;{{line.name}}&amp;lt;/td&amp;gt;
    &amp;lt;td class=&quot;pct&quot;&amp;gt;{{line.value}} %&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;tbody&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Sur ce principe on peut afficher n&#39;importe quel tableau, aussi complexe soit-il.&lt;br /&gt;
&lt;br /&gt;
Donc ce qu&#39;il faut retenir, c&#39;est que la balise &amp;lt;tbody&amp;gt; est très pratique pour regrouper des &amp;lt;tr&amp;gt; qui doivent être répétés ensemble, mais que dans les cas les plus complexes il ne faut pas hésiter à transformer les données pour se simplifier la vie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/8692483714621350039/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/01/tableaux-angularjs-ngrepeat-tbody.html#comment-form' title='11 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/8692483714621350039'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/8692483714621350039'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/01/tableaux-angularjs-ngrepeat-tbody.html' title='Tableaux complexes avec AngularJS : ng-repeat et tbody'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-6429863278903435871</id><published>2013-01-07T18:00:00.000+01:00</published><updated>2013-01-07T18:00:05.368+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$scope"/><category scheme="http://www.blogger.com/atom/ns#" term="contrôleur"/><category scheme="http://www.blogger.com/atom/ns#" term="évènement"/><title type='text'>AngularJS : Scopes et évènements</title><content type='html'>Le &lt;i&gt;scope &lt;/i&gt;d&#39;AngularJS n&#39;est pas très différent de la notion de &lt;i&gt;scope &lt;/i&gt;du langage JavaScript, ce contexte qui pose parfois problème, lorsque le mot clé&amp;nbsp;&lt;i&gt;this&amp;nbsp;&lt;/i&gt;ne pointe plus vers le bon contexte dans un callback. On utilise &lt;i&gt;$scope&lt;/i&gt; ou &lt;i&gt;$rootScope&lt;/i&gt;, en les injectant dans un contrôleur, pour faire référence respectivement au contexte local du contrôleur ou au contexte global de l&#39;application.&lt;br /&gt;
&lt;br /&gt;
Si les scopes d&#39;AngularJS peuvent être créés manuellement, ils vont surtout être créés automatiquement par le framework lors de l&#39;utilisation de certaines directives ou la déclaration d&#39;un nouveau contrôleur. Ils sont&amp;nbsp;reliés sous la forme d&#39;un arbre hiérarchique avec héritage par prototype. Cette organisation en arbre peut être gênante lorsque l&#39;on veut effectuer une communication directe entre des scopes qui ne sont pas reliés par une relation père/fils.&lt;br /&gt;
&lt;br /&gt;
Comment faire passer des informations d&#39;un scope à un autre quand il n&#39;existe pas de relation directe entre eux?&lt;br /&gt;
&lt;br /&gt;
Nous allons voir comment utiliser l&#39;API des scopes fournie par AngularJS pour envoyer et intercepter des évènements personnalisés.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;h2&gt;
Le scope : KEZAKO!&lt;/h2&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
Le scope est un objet JavaScript correspondant au contexte&amp;nbsp;d’exécution&amp;nbsp;d&#39;une portion de la vue. Il référence à la fois des objets du modèle de données et des méthodes contextuelles.&lt;br /&gt;
&lt;br /&gt;
Dans le cas d&#39;un scope directement relié à un contrôleur (avec l&#39;utilisation de la directive ngController), ce dernier permet d&#39;assurer une&amp;nbsp;persistance&amp;nbsp;des données par son unicité et son partage entre le contrôleur et la portion de vue associée.&lt;br /&gt;
&lt;br /&gt;
Les scopes sont comme je vous l&#39;ai dit plus haut reliés de manière hiérarchique. AngularJS ayant pour vocation d&#39;étendre et de renforcer le langage HTML, la structure d&#39;organisation de ces scopes sous forme d&#39;arbre ressemble très&amp;nbsp;sensiblement&amp;nbsp;à la structure HTML des vues de notre application.&lt;br /&gt;
&lt;br /&gt;
Au sommet de l&#39;arbre, il y a ce que l&#39;on appelle le rootScope. AngularJS met à notre disposition un service &lt;i&gt;$rootScope&lt;/i&gt;&amp;nbsp;qui nous permet d&#39;accéder d&#39;injecter dans n&#39;importe quel contrôleur de notre application le scope racine de notre arbre. Ce service&amp;nbsp;&lt;i&gt;$rootScope&lt;/i&gt;&amp;nbsp;est un objet&amp;nbsp;qui regroupe directement ou indirectement sous la forme d&#39;un arbre tous les objets scopes&lt;i&gt;&amp;nbsp;&lt;/i&gt;initialisés&amp;nbsp;dans notre application.&lt;br /&gt;
&lt;br /&gt;
Une application va posséder plusieurs scopes. Ces derniers sont créés par l&#39;utilisation de certaines directives (ngController, ngRepeat, etc...).&amp;nbsp;A chaque création d&#39;un scope, AngularJS utilise la méthode &lt;i&gt;$new&lt;/i&gt;, ce qui a pour effet de créer un scope et de l&#39;ajouter à &amp;nbsp;la liste des enfants du scope père. La création de ce scope met en place la technique de l&#39;héritage par prototype connu en Javascript. Il sera donc possible d&#39;appeler une méthode du parent depuis le scope enfant.&lt;br /&gt;
&lt;br /&gt;
Passons maintenant aux choses sérieuses!&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Les évènements&lt;/h2&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
Par ce système hiérarchisé de scopes, AngularJS nous donne la possibilité d&#39;envoyer et d&#39;intercepter des évènements personnalisés. Ces évènements sont caractérisés par deux choses.&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Une étiquette qui devra être unique dans notre application pour éviter toute interférence,&lt;/li&gt;
&lt;li&gt;Une liste d&#39;arguments (cette liste peut être null ou compter un nombre indéfini d&#39;argument).&lt;/li&gt;
&lt;/ul&gt;
AngularJS utilise par exemple ce système d&#39;évènement pour la gestion des routes et de l&#39;url (location) et utilise les étiquettes suivantes (liste non exhaustive) :&lt;br /&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;$routeChangeStart&lt;/li&gt;
&lt;li&gt;$routeChangeSuccess&lt;/li&gt;
&lt;li&gt;$routeChangeError&lt;/li&gt;
&lt;li&gt;$routeUpdate&lt;/li&gt;
&lt;li&gt;$locationChangeStart&lt;/li&gt;
&lt;li&gt;$locationChangeSuccess&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Je vais maintenant vous présenter les méthodes disponibles dans l&#39;API que nous fournit l&#39;objet scope d&#39;AngularJS.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h3&gt;
Emettre&amp;nbsp;un évènement&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
Comme je vous l&#39;ai dit précédemment, AngularJS nous donne la possibilité d&#39;envoyer des messages à travers &amp;nbsp;les différents scopes de notre application.&lt;br /&gt;
&lt;br /&gt;
Nous avons deux méthodes qui nous permettent d&#39;émettre un évènement: &lt;i&gt;$emit&lt;/i&gt; (cf &lt;a href=&quot;http://docs.angularjs.org/api/ng.$rootScope.Scope#$emit&quot; target=&quot;_blank&quot;&gt;spec angularjs.org&lt;/a&gt;)&amp;nbsp;et &lt;i&gt;$broadcast&amp;nbsp;&lt;/i&gt;&amp;nbsp;(cf&amp;nbsp;&lt;a href=&quot;http://docs.angularjs.org/api/ng.$rootScope.Scope#$broadcast&quot; target=&quot;_blank&quot;&gt;spec angularjs.org&lt;/a&gt;&lt;span id=&quot;goog_958073619&quot;&gt;&lt;/span&gt;&lt;span id=&quot;goog_958073620&quot;&gt;&lt;/span&gt;&lt;a href=&quot;http://www.blogger.com/&quot;&gt;&lt;/a&gt;).&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Méthode $emit&lt;/h4&gt;
&lt;br /&gt;
Cette méthode&amp;nbsp;&lt;i&gt;$emit&lt;/i&gt;&amp;nbsp;disponible sur les objets de type scope, permet d&#39;envoyer un message à toute la chaîne parente du scope émetteur. La propagation de cet évènement se fait donc de façon ascendante uniquement entre les scopes qui sont directement reliés entre eux.&lt;br /&gt;
La propagation de cet évènement peut être stoppée dès lors qu&#39;il est intercepté en utilisant une des méthodes de l&#39;API Event : &lt;i&gt;stopPropagation()&lt;/i&gt; (nous verrons plus tard dans cet article comment écouter un évènement et accéder à l&#39;objet Event associé).&lt;br /&gt;
&lt;br /&gt;
Le schéma ci-dessous montre la propagation de l&#39;évènement&amp;nbsp;via la méthode&amp;nbsp;&lt;i&gt;$emit&lt;/i&gt;.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrwzQZeKH-dQZMaUl9avKt99dSYxiGqLDuC9mnRWcMY9eNs1NlVl5-2cAL9IctHUnnTtt5_UcKo8KNyplrWoDc1jBsrIYf40ookkhK14LmrE96GhC_bFjRWmuCYrfMCy7OlArw3Gz6yEEQ/s1600/fr_angularjs_hierarchie_scope_emit.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;200&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrwzQZeKH-dQZMaUl9avKt99dSYxiGqLDuC9mnRWcMY9eNs1NlVl5-2cAL9IctHUnnTtt5_UcKo8KNyplrWoDc1jBsrIYf40ookkhK14LmrE96GhC_bFjRWmuCYrfMCy7OlArw3Gz6yEEQ/s200/fr_angularjs_hierarchie_scope_emit.png&quot; width=&quot;186&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h4&gt;
Méthode $broadcast&lt;/h4&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
Cette méthode &lt;i&gt;$broadcast&lt;/i&gt;&amp;nbsp;est également disponible sur les objects de type scope. Elle permet, comme la méthode précédente, d&#39;envoyer un évènement, mais cette fois, cet évènement ne peut être stoppé et est envoyé à travers tous l&#39;arbre descendant des scopes instanciés dans notre application en partant du scope émetteur.&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
Le schéma ci-dessous montre la propagation de l&#39;évènement via la méthode &lt;i&gt;$broadcast&lt;/i&gt;.&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj86eg6kUru0E4G46Qm1M8gRvajsaxNfurky0XJVUzaE9FYpN-iyReKan2gvFtrfD6W8c50S1xjAHPJCamg8lEtlJeUhwP06Hc99s6-jQySjm2Paeq5yEE2oIb2mO2TlceMfXRIg7D8XAFv/s1600/fr_angularjs_hierarchie_scope_broadcast.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;200&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj86eg6kUru0E4G46Qm1M8gRvajsaxNfurky0XJVUzaE9FYpN-iyReKan2gvFtrfD6W8c50S1xjAHPJCamg8lEtlJeUhwP06Hc99s6-jQySjm2Paeq5yEE2oIb2mO2TlceMfXRIg7D8XAFv/s200/fr_angularjs_hierarchie_scope_broadcast.png&quot; width=&quot;185&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h4&gt;
L&#39;envoi de l&#39;évènement&lt;/h4&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Comme expliqué précédemment, les évènements dans AngularJS possèdent une étiquette ainsi qu&#39;une liste optionnelle de paramètres.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Si par exemple vous voulez lever un évènement lorsque des données sont arrivées après un appel AJAX, il est possible de passer le résultat de l&#39;appel en paramètre. On sera alors capable dans l&#39;interception de cet évènement de manipuler le résultat de la requête.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h3&gt;
Intercepter un évènement&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
L&#39;interception d&#39;un évènement sous AngularJS se fait via la méthode &lt;i&gt;$on&amp;nbsp;&lt;/i&gt;(cf &lt;a href=&quot;http://docs.angularjs.org/api/ng.$rootScope.Scope#$on&quot; target=&quot;_blank&quot;&gt;spec angularjs.org&lt;/a&gt;). Cette méthode prend deux paramètres: une étiquette et une fonction qui sera&amp;nbsp;exécutée.&lt;br /&gt;
&lt;br /&gt;
La fonction permet de récupérer l&#39;event courant (qui permet notamment de stopper la propagation, de récupérer les scopes sources, etc.) et les arguments passés lors de l&#39;émission de l&#39;évènement.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Un exemple concret&lt;/h2&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
Pour illustrer un peu le fonctionnement de ces évènements, je vous propose un exemple sous jsFiddle. Il est également possible pour les développeurs un peu aventuriers et curieux de se glisser dans le code d&#39;AngularJS et d&#39;observer comment sont utilisés les évènements que j&#39;ai cités dans le préambule du paragraphe &quot;évènement&quot;.&lt;br /&gt;
&lt;br /&gt;
Pour notre exemple, je vais créer un loader.&lt;br /&gt;
Le contrôleur et la vue s&#39;occupent uniquement d&#39;afficher la barre de chargement.&lt;br /&gt;
Le service Loader s&#39;occupe du chargement et de l&#39;envoi des évènements.&lt;br /&gt;
&lt;br /&gt;
&lt;iframe allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot; src=&quot;http://jsfiddle.net/firehist/suzUq/7/embedded/&quot; style=&quot;height: 300px; width: 100%;&quot;&gt;&lt;/iframe&gt;

&lt;br /&gt;
&lt;br /&gt;
Voilà, j&#39;espère avoir correctement expliqué le fonctionnement des évènements dans AngularJS.&lt;br /&gt;
N&#39;hésitez pas à réagir de façon constructive pour faire avancer cet article et ce blog!&lt;br /&gt;
&lt;br /&gt;
Bon dév&#39;&amp;nbsp;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/6429863278903435871/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2013/01/angularjs-scopes-et-evenements.html#comment-form' title='5 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/6429863278903435871'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/6429863278903435871'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2013/01/angularjs-scopes-et-evenements.html' title='AngularJS : Scopes et évènements'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/01293399064543705328</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrwzQZeKH-dQZMaUl9avKt99dSYxiGqLDuC9mnRWcMY9eNs1NlVl5-2cAL9IctHUnnTtt5_UcKo8KNyplrWoDc1jBsrIYf40ookkhK14LmrE96GhC_bFjRWmuCYrfMCy7OlArw3Gz6yEEQ/s72-c/fr_angularjs_hierarchie_scope_emit.png" height="72" width="72"/><thr:total>5</thr:total><georss:featurename>Montpellier, France</georss:featurename><georss:point>43.610769 3.8767159999999876</georss:point><georss:box>43.518792999999995 3.7153544999999877 43.702745 4.0380774999999876</georss:box></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-2921161343773202937</id><published>2012-12-21T15:34:00.001+01:00</published><updated>2012-12-21T15:38:09.314+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$route"/><category scheme="http://www.blogger.com/atom/ns#" term="contrôleur"/><category scheme="http://www.blogger.com/atom/ns#" term="injection de dépendances"/><category scheme="http://www.blogger.com/atom/ns#" term="promise"/><title type='text'>Initialisations avant le routage avec la propriété resolve</title><content type='html'>On peut avoir besoin dans une application AngularJS de faire des initialisations avant de charger une vue, et surtout avant que le framework instancie son contrôleur. Par exemple si on fait appel à une API nécessitant une authentification, et renvoyant un token pour les requêtes suivantes, on peut vouloir s&#39;authentifier avant tout chargement de vue, pour être sûr d&#39;avoir récupéré le token au préalable.&lt;br /&gt;
&lt;br /&gt;
Comment faire en sorte que le routage d&#39;AngularJS attende la récupération du token d&#39;authentification, qui est bien sûr une opération asynchrone ? C&#39;est ce que permet la propriété&amp;nbsp;&lt;i&gt;&lt;b&gt;resolve&lt;/b&gt;&lt;/i&gt;&amp;nbsp;du second paramètre de la méthode &lt;i&gt;$routeProvider.when()&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
La syntaxe de&amp;nbsp;&lt;i&gt;$routeProvider.when()&lt;/i&gt;&amp;nbsp;est la suivante :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$routeProvider.when(&#39;/view1&#39;, {
    controller:&#39;View1Ctrl&#39;, 
    templateUrl:&#39;view1.html&#39;, 
    resolve: {key1: &#39;Service1&#39;, key2: function2}
});
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Cette propriété&amp;nbsp;&lt;i&gt;resolve&lt;/i&gt;&amp;nbsp;doit être un objet JavaScript, donc une map clef/valeur, dont chaque propriété est soit une chaîne de caractères, soit une fonction. Dans le cas d&#39;une chaîne de caractères, la valeur de la propriété est remplacée par le service publié sous ce nom-là. Si c&#39;est une fonction, alors la fonction est exécutée, et la valeur de la propriété est remplacée par la valeur de retour de la fonction.&lt;br /&gt;
&lt;br /&gt;
On obtient donc une map d&#39;objets, après récupération des services ou exécution des fonctions indiquées. Parmi ces objets, il peut y avoir des &lt;i&gt;promises&lt;/i&gt;. Vous trouverez toutes les explications sur les promises dans le précédent article :&amp;nbsp;&lt;a href=&quot;http://www.frangular.com/2012/12/api-promise-angularjs.html&quot;&gt;l&#39;API Promise d&#39;AngularJS&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
S&#39;il y a une ou plusieurs &lt;i&gt;promises&lt;/i&gt;, alors AngularJS va attendre que toutes les promises soient résolues avant d&#39;instancier le contrôleur et de changer la route. C&#39;est donc ainsi qu&#39;on va pouvoir attendre la fin d&#39;une ou plusieurs opérations asynchrones, il suffit de passer les promises correspondant à ces opérations, et AngularJS fera le reste.&lt;br /&gt;
&lt;br /&gt;
Faisons un premier exemple avec un service qui est une promise. Le &lt;a href=&quot;http://jsfiddle.net/tchatel/fupnj/&quot; target=&quot;_blank&quot;&gt;jsFiddle complet est ici&lt;/a&gt;. Pour faire simple puisqu&#39;il ne s&#39;agit que d&#39;un exemple, la factory du service utilise &lt;i&gt;$timeout&lt;/i&gt;&amp;nbsp;pour créer une promise qui sera résolue au bout de 3 secondes, et renvoie cette promise. C&#39;est-à-dire que l&#39;objet publié sous le nom de service &#39;WaitInit&#39; est directement la promise renvoyée par &lt;i&gt;$timeout&lt;/i&gt; :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;app.factory(&#39;WaitInit&#39;, [&#39;$timeout&#39;, function ($timeout) {
    return $timeout(function () {}, 3000);
}]);

&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Du coup dans la propriété &lt;i&gt;resolve&lt;/i&gt;, on peut mettre directement le nom de ce service, et comme c&#39;est une promise, AngularJS va attendre qu&#39;elle soit résolue pour instancier le contrôleur et charger la vue :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$routeProvider.
    when(&#39;/&#39;, {
        controller:&#39;View1Ctrl&#39;, 
        templateUrl:&#39;view1.html&#39;, 
        resolve: { wait: &#39;WaitInit&#39;}
    }).
    when(&#39;/view2&#39;, {
        controller:&#39;View2Ctrl&#39;, 
        templateUrl:&#39;view2.html&#39;, 
        resolve: { wait: &#39;WaitInit&#39;}
    }).
    otherwise({redirectTo:&#39;/&#39;});
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
C&#39;est ce qu&#39;on constate à l&#39;exécution, il y a un délai de 3 secondes avant que la première vue apparaisse. Ensuite lors des changements de vue il n&#39;y a plus de délai, car le service est toujours la même promise, qui est déjà résolue. On peut donc ainsi, à la place du &lt;i&gt;$timeout&lt;/i&gt;&amp;nbsp;de l&#39;exemple, faire les initialisations nécessaires à l&#39;application et attendre qu&#39;elles soient terminées avant d&#39;afficher la première vue.&lt;br /&gt;
&lt;br /&gt;
Voyons comme utiliser l&#39;option des fonctions plutôt que des services, qui est plus générale. Voici &lt;a href=&quot;http://jsfiddle.net/tchatel/pph7P/&quot; target=&quot;_blank&quot;&gt;un second jsFiddle&lt;/a&gt; avec des fonctions anonymes, dont le code est directement dans la propriété &lt;i&gt;resolve&lt;/i&gt;, et qui renvoient simplement le service précédent :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;        resolve: { func: [&#39;WaitInit&#39;, function (WaitInit) {
            return WaitInit;
        }]}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Rien n&#39;oblige bien sûr à utiliser une fonction anonyme, surtout si c&#39;est la même fonction qui sert à toutes les vues, il est beaucoup plus logique de la définir une seule fois et de l&#39;utiliser dans tous les &lt;i&gt;resolve&lt;/i&gt;. Il faut juste faire attention à l&#39;injection des dépendances, si la fonction a besoin d&#39;utiliser des services. L&#39;injection doit se faire dans la fonction elle-même. Il n&#39;est pas possible d&#39;injecter des services à la fonction passée comme paramètre de la méthode &lt;i&gt;config()&lt;/i&gt;, qui n&#39;accepte que des injections de providers.&lt;br /&gt;
&lt;br /&gt;
Mais si on injecte directement les services à la fonction qu&#39;on passe au &lt;i&gt;resolve&lt;/i&gt;, il n&#39;y a aucun problème, comme on peut le voir dans &lt;a href=&quot;http://jsfiddle.net/tchatel/QRMMN/&quot; target=&quot;_blank&quot;&gt;ce troisième jsFiddle&lt;/a&gt; :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;app.config(function($routeProvider) {
    
  function wait(WaitInit) {
      return WaitInit;
  }
  wait.$inject = [&#39;WaitInit&#39;];
    
  $routeProvider.
    when(&#39;/&#39;, {
        controller:&#39;View1Ctrl&#39;, 
        templateUrl:&#39;view1.html&#39;, 
        resolve: { func: wait}
    }).
    when(&#39;/view2&#39;, {
        controller:&#39;View2Ctrl&#39;, 
        templateUrl:&#39;view2.html&#39;, 
        resolve: { func: wait}
    }).
    otherwise({redirectTo:&#39;/&#39;});
    
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Voilà pour les différentes façons d&#39;utiliser cette propriété &lt;i&gt;resolve &lt;/i&gt;avec des promises, fournies comme des services ou des fonctions. Mais son usage ne s&#39;arrête pas là, et on n&#39;a pas vu à quoi servent les clefs de cette map clef/valeur.&lt;br /&gt;
&lt;br /&gt;
Une fois que les objets de la map ont été récupérés soit depuis les services correspondants, soit en exécutant les fonctions, AngularJS attend que toutes les promises soient résolues, mais il remplace également chaque promise lorsqu&#39;elle est résolue par son résultat, sa valeur de résolution. A ce stade-là, il ne reste plus aucune promise dans la map, et les valeurs de cette map sont disponibles pour l&#39;injection dans le contrôleur de la vue, comme on injecte n&#39;importe quel service, en utilisant la clef associée à chaque valeur.&lt;br /&gt;
&lt;br /&gt;
On peut donc ainsi avoir un même contrôleur qui sert à plusieurs vues, mais avec des valeurs injectées différentes selon la vue. Rappelez-vous quand même que ce qu&#39;on associe à chaque clef du &lt;i&gt;resolve&lt;/i&gt;, c&#39;est soit le nom d&#39;un service, soit une fonction renvoyant une valeur ; on ne peut pas y mettre directement la valeur souhaitée, mais il suffit de faire une fonction renvoyant la valeur.&lt;br /&gt;
&lt;br /&gt;
C&#39;est ce qui est fait dans &lt;a href=&quot;http://jsfiddle.net/tchatel/GKYUN/&quot; target=&quot;_blank&quot;&gt;ce dernier jsFiddle&lt;/a&gt;, où il n&#39;y a plus de promise, mais où l&#39;on passe au même contrôleur une fonction renvoyant une valeur différente selon la vue :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$routeProvider.
    when(&#39;/&#39;, {
        controller:&#39;ViewCtrl&#39;, 
        templateUrl:&#39;view1.html&#39;, 
        resolve: { viewName: function () {
            return &quot;V1&quot;;
        }}
    }).
    when(&#39;/view2&#39;, {
        controller:&#39;ViewCtrl&#39;, 
        templateUrl:&#39;view2.html&#39;, 
        resolve: { viewName: function () {
            return &quot;V2&quot;;
        }}
    }).
    otherwise({redirectTo:&#39;/&#39;});
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Et l&#39;injection de &lt;i&gt;viewName&lt;/i&gt;&amp;nbsp;dans le contrôleur se fait de façon classique :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;app.controller(&#39;ViewCtrl&#39;, [&#39;$scope&#39;, &#39;viewName&#39;, function ($scope, viewName) {
    $scope.view = viewName;
}]);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
J&#39;ai utilisé une fonction dans l&#39;exemple, mais pour implémenter quelque chose ressemblant au design pattern &lt;i&gt;Strategy&lt;/i&gt;, où l&#39;on passerait du code différent au même contrôleur en fonction de la vue, il est beaucoup plus propre d&#39;écrire chaque stratégie sous la forme d&#39;un service, et d&#39;utiliser &lt;i&gt;resolve&lt;/i&gt;&amp;nbsp;avec le nom du service à injecter au contrôleur pour chacune des vues.&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/2921161343773202937/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2012/12/initialisations-avant-routage-avec-resolve.html#comment-form' title='6 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/2921161343773202937'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/2921161343773202937'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2012/12/initialisations-avant-routage-avec-resolve.html' title='Initialisations avant le routage avec la propriété resolve'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-6618493494355801381</id><published>2012-12-15T19:47:00.001+01:00</published><updated>2012-12-16T17:17:51.784+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="$http"/><category scheme="http://www.blogger.com/atom/ns#" term="$q"/><category scheme="http://www.blogger.com/atom/ns#" term="promise"/><title type='text'>L&#39;API Promise d&#39;AngularJS</title><content type='html'>Les services standards d&#39;AngularJS&amp;nbsp;&lt;i&gt;$timeout&lt;/i&gt;&amp;nbsp;et surtout&amp;nbsp;&lt;i&gt;$http&amp;nbsp;&lt;/i&gt;renvoient tous deux des &lt;i&gt;promises&lt;/i&gt;, qui sont très pratiques pour gérer des opérations asynchrones. Cette notion de &lt;i&gt;promise&lt;/i&gt;&amp;nbsp;existe dans d&#39;autres frameworks, comme jQuery, et AngularJS intègre une implémentation de cette API. Elle peut être utilisée par les développeurs dans l&#39;écriture des leurs propres services pour simplifier la gestion des actions asynchrones. C&#39;est très important de bien comprendre comment fonctionne cette API, qui est probablement la partie la plus ardue d&#39;AngularJS, pour tirer profit de toute la puissance du service $http, et pour gérer facilement les enchaînements d&#39;opérations asynchrones dans une application.&lt;br /&gt;
&lt;br /&gt;
Décrire en français le fonctionnement de l&#39;API de &lt;i&gt;promises&amp;nbsp;&lt;/i&gt;ne va pas être simple, car il est difficile de traduire de façon élégante les notions qu&#39;elle recouvre sans s&#39;éloigner des termes anglais utilisés comme noms de méthodes. Donc tant pis pour l&#39;élégance, je vais m&#39;en tenir aux termes anglais ou à des traductions mot-à-mot, pour coller au plus près aux noms des méthodes disponibles.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Qu&#39;est-ce qu&#39;une promise ?&amp;nbsp;&lt;/h2&gt;
Une &lt;i&gt;promise&lt;/i&gt;&amp;nbsp;(une “promesse” en anglais) est un&amp;nbsp;objet JavaScript correspondant au résultat différé d&#39;une opération asynchrone.&lt;br /&gt;
&lt;br /&gt;
Imaginons une fonction qui doit déclencher une opération prenant un certain temps, et qui pourra soit réussir et fournir un résultat, soit échouer et balancer une exception. Ça peut être une fonction synchrone, mais elle va devoir bloquer l&#39;exécution jusqu&#39;à ce que l&#39;opération soit terminée, pour selon le cas renvoyer la valeur de retour ou balancer l&#39;exception.&lt;br /&gt;
&lt;br /&gt;
Dans un langage multi-thread on peut envisager éventuellement de bloquer ainsi l&#39;exécution, pas en JavaScript. Du coup on va préférer un fonctionnement asynchrone : la fonction va juste démarre l&#39;opération, et renvoyer immédiatement une &lt;i&gt;promise&lt;/i&gt;, une promesse de résultat différé, qui sera résolue ultérieurement comme une valeur de retour ou comme une cause d&#39;erreur, l&#39;équivalent d&#39;une exception.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
Les promises&amp;nbsp;ont une méthode &lt;i&gt;then()&lt;/i&gt;, c&#39;est même à ça qu&#39;on les reconnaît. L&#39;API spécifie, et c&#39;est ce qui est fait dans AngularJS, qu&#39;un objet est considéré comme une promise si et seulement si il a une méthode &lt;i&gt;then()&lt;/i&gt;. Cette méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;prend deux fonctions comme paramètres, un &lt;i&gt;successCallback&lt;/i&gt;&amp;nbsp;et un &lt;i&gt;errorCallback&lt;/i&gt;, et l&#39;un ou l&#39;autre sera appelé selon si la promise est résolue avec succès ou en erreur :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;promise.then(function(greeting) {
    alert(&#39;Success: &#39; + greeting);
}, function(reason) {
    alert(&#39;Failed: &#39; + reason);
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
L&#39;API des promises&amp;nbsp;garantit que :&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;d&#39;une promise&amp;nbsp;prend deux callbacks en paramètres, tous deux optionnels ; le premier sera appelé si la promise&amp;nbsp;est résolue avec succès, le second en cas d&#39;erreur.&lt;/li&gt;
&lt;li&gt;on peut enregistrer plusieurs paires de callbacks (succès et erreur), en appelant plusieurs fois la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;d&#39;une promise&lt;/li&gt;
&lt;li&gt;si plusieurs paires de callbacks ont été enregistrées sur une même promise, la résolution de cette promise déclenchera une seule fois le callback adéquat de chacune des paires, et &lt;b&gt;forcément dans l&#39;ordre où les paires de callbacks ont été enregistrées&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;on peut appeler la méthode&amp;nbsp;&lt;i&gt;then()&lt;/i&gt;&amp;nbsp;d&#39;une&amp;nbsp;promise&amp;nbsp;déjà résolue, dans ce cas le callback adéquat sera appelé &lt;b&gt;immédiatement &lt;/b&gt;- mais jamais avant la fin de la méthode&amp;nbsp;&lt;i&gt;then()&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;une promise ne peut être résolue qu&#39;une seule fois, donc tous les callbacks verront le même résultat, ou la même erreur&lt;/li&gt;
&lt;li&gt;la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;renvoie une nouvelle promise, qui représente le résultat différé du callback appelé&lt;/li&gt;
&lt;li&gt;les promises peuvent être utilisées dans les vues AngularJS comme des données différées, qui seront automatiquement prises en compte dans les bindings lors de la résolution de chaque promise&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
Tous les points sont importants dans cette liste. Mais un premier aspect fondamental de l&#39;API des promises est indiqué dans le point 4 : on peut enregistrer un callback n&#39;importe quand, sans se préoccuper de savoir si la promise est déjà résolue, autrement dit si l&#39;opération asynchrone s&#39;est déjà terminée ou est encore en cours d&#39;exécution. Peu importe, on enregistre une paire de callbacks sur la promise, et on est sûr que le callback approprié sera appelé dès que possible.&lt;br /&gt;
&lt;br /&gt;
Les points 6 et 7 ont de grosses conséquences, et nous allons les détailler.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Enchaînement de promises&lt;/h2&gt;
Commençons par le point 6 : c&#39;est ce qui permet de chaîner des promises. La méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;renvoie une seconde promise, dont l&#39;opération asynchrone est le callback de la première promise. Si ce callback renvoie une valeur quelconque, la promise retournée par &lt;i&gt;then()&lt;/i&gt; sera résolue dès la fin du callback. Mais, et là accrochez-vous au pinceau j&#39;enlève l&#39;échelle, le callback peut lui-même renvoyer une autre promise. Nous voilà avec trois promises dans l&#39;histoire, on va prendre un exemple avec des noms de variables pour que ce soit compréhensible :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var promise2 = promise1.then(function () {
    ...
    return callbackPromise;
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Dans cette exemple où la fonction callback renvoie une promise &lt;i&gt;callbackPromise&lt;/i&gt;, la promise &lt;i&gt;promise2&amp;nbsp;&lt;/i&gt;retournée par&lt;i&gt; then()&lt;/i&gt; sera chaînée à&amp;nbsp;&lt;i&gt;callbackPromise&lt;/i&gt;, et résolue lorsque&amp;nbsp;&lt;i&gt;callbackPromise&lt;/i&gt;&amp;nbsp;sera résolue. En pratique dans le code d&#39;AngularJS, la promise retournée par &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;est toujours chaînée à la valeur retour du callback, qui si ce n&#39;est pas une promise est encapsulée dans une promise résolue en succès avec cette valeur-là.&lt;br /&gt;
&lt;br /&gt;
Donc si le callback déclenche une opération asynchrone, il faut absolument qu&#39;il renvoie une promise qui ne sera résolue que lorsque l&#39;opération asynchrone sera complètement terminée. Si le callback ne renvoie rien, ou une valeur qui n&#39;est pas une promise, l&#39;enchaînement se fera trop tôt, sans attendre la fin de l&#39;opération asynchrone. Il vaut mieux gérer correctement le retour du callback même s&#39;il n&#39;y a rien à enchaîner derrière, comme ça le jour où l&#39;on aura besoin d&#39;ajouter un enchaînement ça marchera directement. On évite ainsi de créer une cause d&#39;erreur potentielle, sur laquelle on butera certainement un jour.&lt;br /&gt;
&lt;br /&gt;
Revenons-en à cette seconde promise retournée par &lt;i&gt;then()&lt;/i&gt;. On peut évidemment lui enregistrer un nouveau callback, ou une paire de callbacks, en appelant &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;qui renvoie alors une troisième promise. Et ainsi de suite. On peut ainsi enchaîner très facilement sous la forme d&#39;un code linéaire des opérations asynchrones, dont chacune ne démarrera qu&#39;à la fin de la précédente, et seulement s&#39;il n&#39;y a eu aucune erreur dans toutes les opérations précédentes :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;promise.then(step1)
       .then(step2)
       .then(step3)
       .then(step4)
       .then(null, function (error) {
           // Handle any error from step1 through step4
       });&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Les quatre premiers appels à &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;ne passent que le premier paramètre, le callback à appeler en cas de succès. Les deux paramètres de &lt;i&gt;then()&lt;/i&gt; sont optionnels. Pour une promise qui sera résolue en erreur, si l&#39;on appelle sa méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;sans second paramètre, alors ça ne déclenchera pas le callback d&#39;erreur puisqu&#39;il est absent, mais la nouvelle promise qui a été renvoyée par &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;sera elle aussi résolue en erreur, avec la même cause d&#39;erreur. Il faut voir ça comme une exception qui remonte dans la pile d&#39;appel des fonctions, et voir le callback d&#39;erreur comme un &lt;i&gt;catch&lt;/i&gt;. Dans un tel enchaînement de promises sans traitement d&#39;erreur, si une erreur survient, toutes les promises suivante vont zapper le callback de succès. Ici s&#39;il y a une erreur dans la fonction &lt;i&gt;step2&lt;/i&gt;, alors les fonctions &lt;i&gt;step3 &lt;/i&gt;et &lt;i&gt;step4 &lt;/i&gt;ne seront pas appelées, et seul le callback d&#39;erreur qui est dans le dernier &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;sera exécuté. Bien sûr s&#39;il y avait eu un callback d&#39;erreur avec la fonction &lt;i&gt;step3&lt;/i&gt;, c&#39;est celui-là qui aurait été exécuté, mais il est souvent pertinent de ne faire la gestion des erreurs que de façon globale à la fin de l&#39;enchaînement.&lt;br /&gt;
&lt;br /&gt;
Sans promises, si l&#39;on veut faire la même chose directement avec des callbacks, pour un tel exemple on se retrouve avec 4 niveaux de callbacks imbriqués, et la gestion des erreurs à faire dans les 4 niveaux. Et si le nombre d&#39;étapes est variable, il faudrait faire appel à de la récursivité, alors qu&#39;avec des promises une simple boucle suffit. L&#39;utilisation des promises permet de mettre le code à plat, presque comme s&#39;il était synchrone, avec en plus la propagation des erreurs. C&#39;est une très grosse simplification.&lt;br /&gt;
&lt;br /&gt;
Car ce besoin d&#39;enchaîner des opérations asynchrones, c&#39;est quelque chose de très courant dans une application cliente JavaScript. Typiquement, quand on accède à une API web de type REST, on va régulièrement devoir enchaîner des requêtes HTTP, parce qu&#39;une requête aura besoin d&#39;une valeur retournée par une ou plusieurs requêtes précédentes. Autant dire que si l&#39;on veut utiliser des API de type REST avec AngularJS, mieux vaut maîtriser les promises, pour ne pas se noyer dans de multiples niveaux de callbacks imbriqués.&lt;br /&gt;
&lt;br /&gt;
Je disais que le callback d&#39;erreur est l&#39;équivalent d&#39;un &lt;i&gt;catch&lt;/i&gt;. L&#39;analogie va plus loin, car le callback d&#39;erreur s&#39;il est au milieu d&#39;un enchaînement, peut soit arriver à corriger l&#39;erreur et permettre l&#39;exécution normale des étapes suivantes, soit ne pas arriver à la corriger et balancer une nouvelle erreur, comme un &lt;i&gt;throw&lt;/i&gt;&amp;nbsp;ou &lt;i&gt;rethrow&lt;/i&gt;. Car la promise renvoyée par la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;sera chaînée de la même façon à la promise retournée par le callback, que ce soit le callback de succès ou le callback d&#39;erreur qui ait été appelé. Si le callback d&#39;erreur renvoie une promise résolue en erreur, ça veut dire que l&#39;erreur persiste et n&#39;a pas pu être corrigée, mais s&#39;il renvoie autre chose, une valeur qui n&#39;est pas une promise, ou une promise qui sera résolue en succès, ou même s&#39;il ne renvoie rien, alors il n&#39;y a plus d&#39;erreur et la promise suivante de l&#39;enchaînement sera résolue en succès. Et s&#39;il survient une exception dans le callback et qu&#39;elle n&#39;est pas interceptée, alors la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;l&#39;intercepte et l&#39;encapsule dans une promise résolue en erreur pour la suite de l&#39;enchaînement.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Le promise manager : $q.defer()&lt;/h2&gt;
J&#39;ai parlé jusque là des promises et de la façon de les enchaîner, sans expliquer comment on peut créer une promise correspondant à une opération asynchrone.&lt;br /&gt;
&lt;br /&gt;
C&#39;est là qu&#39;intervient le &lt;i&gt;promise manager&lt;/i&gt;. On crée un promise manager en appelant la méthode &lt;i&gt;defer()&lt;/i&gt;&amp;nbsp;du service &lt;i&gt;$q&lt;/i&gt;&amp;nbsp;d&#39;AngularJS. Ce service tire son nom de la&lt;i&gt;&amp;nbsp;“Q Library” &lt;/i&gt;de&amp;nbsp;Kris Kowal, dont est directement inspirée l&#39;implémentation des promises dans AngularJS.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;$q.defer()&lt;/i&gt;&amp;nbsp;crée un promise manager, selon le terme employé dans la spécification de Kris Kowal. C&#39;est un objet qui a une propriété &lt;i&gt;promise&lt;/i&gt;&amp;nbsp;contenant logiquement la promise associée, ainsi que deux méthodes :&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;i&gt;resolve(value)&lt;/i&gt;&amp;nbsp;qui sert à résoudre la promise associée, en lui fournissant la valeur de retour (le résultat de l&#39;opération)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;reject(reason)&lt;/i&gt;&amp;nbsp;qui sert à résoudre en erreur la promise, en fournissant la raison de l&#39;erreur&lt;/li&gt;
&lt;/ul&gt;
L&#39;appel de la méthode &lt;i&gt;reject()&lt;/i&gt;&amp;nbsp;entraîne forcément un rejet de la promise, c&#39;est-à-dire sa résolution en erreur.&lt;br /&gt;
&lt;br /&gt;
Pour la méthode &lt;i&gt;resolve()&lt;/i&gt;&amp;nbsp;c&#39;est plus compliqué. Si la valeur passée n&#39;est pas une promise, alors la promise associée au promise manager est résolue en succès, avec cette valeur comme résultat. Mais si on passe une autre promise, appelons-là &lt;i&gt;promise0&lt;/i&gt;, à la méthode &lt;i&gt;resolve()&lt;/i&gt;, alors c&#39;est seulement quand &lt;i&gt;promise0&lt;/i&gt;&amp;nbsp;sera résolue que la promise associée au promise manager sera elle-aussi résolue de la même façon, en succès avec la même valeur si &lt;i&gt;promise0&amp;nbsp;&lt;/i&gt;est résolue en succès, ou en erreur avec la même cause si &lt;i&gt;promise0&lt;/i&gt;&amp;nbsp;est résolue en erreur. On retrouve ici le mécanisme qui sert à chaîner les promises.&lt;br /&gt;
&lt;br /&gt;
En fait la méthode &lt;i&gt;reject()&lt;/i&gt;&amp;nbsp;n&#39;est qu&#39;un cas particulier de &lt;i&gt;resolve()&lt;/i&gt;&amp;nbsp;puisque ça revient à appeler &lt;i&gt;resolve()&lt;/i&gt;&amp;nbsp;en encapsulant la cause d&#39;erreur dans une promise rejetée en erreur, c&#39;est exactement ce qui est fait dans le code d&#39;AngularJS (méthode &lt;i&gt;reject() &lt;/i&gt;de l&#39;objet renvoyé par &lt;i&gt;$q.defer()&lt;/i&gt;)&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;    reject: function(reason) {
        deferred.resolve(reject(reason));
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Une telle promise rejetée en erreur peut être créée directement grâce au service $q :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$q.reject(reason);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Prenons un exemple tout simple avec une opération différée via un setTimeout(). Il a l&#39;avantage d&#39;être simple, mais en pratique c&#39;est quelque chose qu&#39;on n&#39;écrira jamais ainsi, car AngularJS fournit le service $timeout pour ça. Peu importe, c&#39;est un exemple pour montrer le mécanisme, et pas un exemple de ce qu&#39;il faut faire (&lt;a href=&quot;http://jsfiddle.net/tchatel/6B9F4/&quot; target=&quot;_blank&quot;&gt;le jsFiddle est ici&lt;/a&gt;) :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var AppCtrl = function($scope, $q) {
    var deferred = $q.defer();
    setTimeout(function() {
        $scope.$apply(function() {
            deferred.resolve(&quot;terminé !&quot;);
        });
    }, 3000);
    var promise = deferred.promise;

    promise.then(function(result) {
        $scope.result = result;
    });
}​&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Le contenu de la fonction différée par &lt;i&gt;setTimeout()&lt;/i&gt;&amp;nbsp;est encapsulé dans un &lt;i&gt;$scope.$apply()&lt;/i&gt;, pour que le code soit exécuté dans le contexte d&#39;AngularJS. C&#39;est justement pour ne pas avoir à faire ça qu&#39;on utilisera en pratique plutôt le service &lt;i&gt;$timeout&lt;/i&gt;. Donc dans cet exemple, on crée un objet &lt;i&gt;deferred&lt;/i&gt;, un manager de promise, et on attache un callback à la promise grâce à sa méthode &lt;i&gt;then()&lt;/i&gt;. Quand le timeout de 3 secondes s&#39;achève, le manager résout la promise, en succès avec comme valeur la chaîne &lt;i&gt;&quot;terminé !&quot;&lt;/i&gt;. Ce qui déclenche le callback enregistré avec &lt;i&gt;then()&lt;/i&gt;, la valeur est mise dans le scope, et s&#39;affiche dans la vue.&lt;br /&gt;
&lt;br /&gt;
Le service &lt;i&gt;$q&lt;/i&gt;&amp;nbsp;fournit deux autres méthodes intéressantes.&amp;nbsp;&lt;i&gt;$q.&lt;b&gt;when&lt;/b&gt;(value)&lt;/i&gt;&amp;nbsp;encapsule la valeur passée en paramètre dans une promise. Ca permet de créer une promise déjà résolue en succès avec une certaine valeur, par exemple pour servir de point de départ pour l&#39;enchaînement de plusieurs étapes :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var startPromise = $q.when(value);
startPromise.then(step1)
            .then(step2)
            .then(step3)
            .then(step4)
            .then(null, function (error) {
                // Handle any error from step1 through step4
            });
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Si on passe à la méthode&amp;nbsp;&lt;i&gt;$q.when()&lt;/i&gt;&amp;nbsp;un objet qui est déjà une promise, elle renvoie non pas la promise telle quelle, mais une nouvelle promise équivalente car elle est chaînée à celle passée en paramètre, donc qui sera résolue en même temps et de la même façon.&lt;br /&gt;
&lt;br /&gt;
La dernière méthode,&amp;nbsp;&lt;i&gt;$q.&lt;b&gt;all&lt;/b&gt;([promise1, promise2, promise3]),&lt;/i&gt;&amp;nbsp;sert à créer une promise qui sera résolue lorsque toutes les promises du tableau passé en paramètre seront elles-mêmes résolues. S&#39;il y a dans le tableau des valeurs qui ne sont pas des promises, chacune est encapsulée dans une promise déjà résolue en succès avec la valeur en question, comme le ferait la méthode &lt;i&gt;$q.when()&lt;/i&gt;. Si toutes les promises du tableau sont résolues en succès, alors la promise renvoyé par &lt;i&gt;$q.all()&lt;/i&gt;&amp;nbsp;est résolue en succès, avec comme valeur résultat un tableau contenant les résultats de chacune des promises, dans le même ordre bien sûr. Si une promise du tableau passé à &lt;i&gt;$q.all()&lt;/i&gt;&amp;nbsp;est rejetée en échec, alors la promise renvoyée est elle aussi rejetée en échec, avec la même cause.&lt;br /&gt;
&lt;br /&gt;
A quoi ça sert ? Tout simplement à créer un point de synchronisation, dans un enchaînement qui n&#39;est plus simplement linéaire. On peut avoir une opération qui dépend du résultat de plusieurs autres opérations, lesquelles peuvent être simultanées. Avec &lt;i&gt;$q.all()&lt;/i&gt;, on crée une promise qui sera résolue quand toutes les opérations simultanées seront terminées, et on y attache l&#39;opération suivante, qui sera alors exécutée dès que possible mais forcément après tous ses prérequis. Ça peut être typiquement une requête HTTP à un service web, qui a besoin d&#39;informations fournies par plusieurs requêtes préalables.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Les promises du service $http&lt;/h2&gt;
Une requête HTTP étant clairement une opération asynchrone, c&#39;est donc en toute logique que le service &lt;i&gt;$http&lt;/i&gt; d&#39;AngularJS renvoie une promise. Qu&#39;on utilise directement la fonction &lt;i&gt;$http()&lt;/i&gt; en lui passant des paramètres, où l&#39;une des méthodes simplifiées &lt;i&gt;$http.get()&lt;/i&gt;, &lt;i&gt;$http.post()&lt;/i&gt;, etc., toutes renvoient une promise.&lt;br /&gt;
&lt;br /&gt;
Il s&#39;agit d&#39;une vraie promise, donc avec une méthode &lt;i&gt;then()&lt;/i&gt;, mais qui dispose aussi de deux méthodes supplémentaires, &lt;i&gt;success()&lt;/i&gt;&amp;nbsp;et &lt;i&gt;error()&lt;/i&gt;&amp;nbsp;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$http({method: &#39;GET&#39;, url: &#39;/someUrl&#39;}).
    success(function(data, status, headers, config) {
        // this callback will be called asynchronously
        // when the response is available
    }).
    error(function(data, status, headers, config) {
        // called asynchronously if an error occurs
        // or server returns response with status
        // code outside of the [200,300[ interval
    });&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
On pourrait utiliser la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;pour enregistrer les callbacks traitant le retour de la requête HTTP, suivant si c&#39;est un succès ou une erreur. Mais les méthodes &lt;i&gt;success()&lt;/i&gt;&amp;nbsp;et &lt;i&gt;error()&lt;/i&gt;&amp;nbsp;sont un peu plus pratiques, car le callback qu&#39;on leur passe reçoit directement quatre paramètres qui décomposent le contenu de la réponse HTTP, parsé si c&#39;était du JSON, le statut HTTP, les headers et la configuration.&lt;br /&gt;
&lt;br /&gt;
Tandis que si on utilise la méthode &lt;i&gt;then()&lt;/i&gt;, le callback appelé recevra en paramètre un objet avec quatre propriétés : &lt;i&gt;data&lt;/i&gt;, &lt;i&gt;status&lt;/i&gt;, &lt;i&gt;headers&lt;/i&gt; et &lt;i&gt;config&lt;/i&gt;. Donc ce n&#39;est qu&#39;un petit raccourci, qui évite d&#39;aller chercher dans les propriétés d&#39;un unique objet paramètre.&lt;br /&gt;
&lt;br /&gt;
Par contre là où ça diffère davantage, c&#39;est au niveau des valeurs renvoyées. Alors que la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;crée et renvoie une nouvelle promise chaînée au retour du callback, les méthodes &lt;i&gt;success()&lt;/i&gt;&amp;nbsp;et &lt;i&gt;error()&lt;/i&gt;&amp;nbsp;ne créent pas de nouvelle promise, et renvoient l&#39;objet &lt;i&gt;this&lt;/i&gt;&amp;nbsp;sur lequel elles sont appelées, c&#39;est-à-dire la promise créée par&lt;i&gt; $http()&lt;/i&gt; ou &lt;i&gt;$http.get()&lt;/i&gt;&amp;nbsp;ou la forme utilisée quelle qu&#39;elle soit.&lt;br /&gt;
&lt;br /&gt;
C&#39;est pour ça qu&#39;on peut faire suivre les appels à &lt;i&gt;success()&lt;/i&gt;&amp;nbsp;et &lt;i&gt;error()&lt;/i&gt;, dans l&#39;ordre qu&#39;on veut d&#39;ailleurs. Mais c&#39;est aussi pour ça que les méthodes &lt;i&gt;success()&lt;/i&gt;&amp;nbsp;et &lt;i&gt;error()&lt;/i&gt;, contrairement à la méthode &lt;i&gt;then()&lt;/i&gt;, ne permettent pas d&#39;enchaîner des requêtes HTTP sous la forme d&#39;un chaînage de promises.&lt;br /&gt;
&lt;br /&gt;
Rien n&#39;empêche de combiner ces deux types de méthodes, d&#39;utiliser &lt;i&gt;success()&lt;/i&gt;&amp;nbsp;et éventuellement&amp;nbsp;&lt;i&gt;error()&lt;/i&gt;&amp;nbsp;pour traiter le résultat de la requête, et la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;de la même promise pour l&#39;enchaînement de la requête suivante. Voyons ça au travers d&#39;un exemple, qui consiste à enchaîner deux requêtes à l&#39;API de Twitter - l&#39;ancienne API en réalité, qui ne demandait pas d&#39;être authentifié, c&#39;est plus simple et pour l&#39;instant elle marche encore. La première requête va cherche les derniers tweets contenant la chaîne de caractères &quot;AngularJS&quot;. On ne garde que le tweet le plus récent, c&#39;est-à-dire l&#39;élément 0 du tableau reçu car ils sont triés par date décroissante, pour trouver son auteur. Et on fait une seconde requête qui récupère les derniers tweets de cet auteur-là. L&#39;intérêt n&#39;est pas flagrant, mais c&#39;est juste pour montrer un enchaînement de requêtes, la seconde utilisant des données récupérées par la première.&lt;br /&gt;
&lt;br /&gt;
On peut faire la première requête, puis la seconde à l&#39;intérieur du callbalk passé à la méthode &lt;i&gt;success()&lt;/i&gt;&amp;nbsp;de la première. C&#39;est ce qui est fait dans &lt;a href=&quot;http://jsfiddle.net/tchatel/QWttG/&quot; target=&quot;_blank&quot;&gt;ce premier jsFiddle&lt;/a&gt; :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var deferred = $q.defer();

var url = &#39;http://search.twitter.com/search.json&#39;
        + &#39;?q=angularjs&amp;amp;rpp=100&amp;amp;include_entities=true&amp;amp;result_type=mixed&#39;
        + &#39;&amp;amp;callback=JSON_CALLBACK&#39;;
$http.jsonp(url).
    success(function(data, status) {
        var lastTweet = data.results[0];
        var user = {
            screenName: lastTweet.from_user,
            name: lastTweet.from_user_name,
            image: lastTweet.profile_image_url
        };
        var url = &#39;https://api.twitter.com/1/statuses/user_timeline.json&#39;
                + &#39;?screen_name=&#39;
                + encodeURIComponent(lastTweet.from_user)
                + &#39;&amp;amp;callback=JSON_CALLBACK&#39;;
        $http.jsonp(url).
            success(function(data, status) {
                deferred.resolve({user: user, lastTweets: data});
            }).error(function(data, status) {
                deferred.reject(data);
            });
    }).
    error(function(data, status) {
        deferred.reject(data);
    });

return deferred.promise;            
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
L&#39;ensemble est dans un service qui renvoie une promise, créée en appelant &lt;i&gt;$q.defer()&lt;/i&gt;. Ça marche, mais on voit bien qu&#39;on a dans le code un niveau d&#39;imbrication supplémentaire pour chaque nouvelle requête à enchaîner, et que la gestion des erreurs doit se faire à chaque requête. Si ce n&#39;est pas critique ici parce qu&#39;il y a seulement deux requêtes, il est difficilement envisageable d&#39;écrire des enchaînements plus complexes de cette façon.&lt;br /&gt;
&lt;br /&gt;
Mais si le code de ce premier exemple est imbriqué, c&#39;est parce qu&#39;on n&#39;a pas utilisé les possibilités d&#39;enchaînement des promises. L&#39;objet renvoyé par &lt;i&gt;$http.jsonp()&lt;/i&gt;&amp;nbsp;est une promise, profitons-en.&lt;br /&gt;
&lt;br /&gt;
Voici &lt;a href=&quot;http://jsfiddle.net/tchatel/bYjvJ/&quot; target=&quot;_blank&quot;&gt;un second jsFiddle&lt;/a&gt;, où l&#39;enchaînement se fait en appelant la méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;de la promise :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var url, user, lastTweets;
var promiseStart = $q.when(&#39;start&#39;);

var promise1 = promiseStart.then(function (value) {
    url = &#39;http://search.twitter.com/search.json&#39;
        + &#39;?q=angularjs&amp;amp;rpp=100&amp;amp;include_entities=true&amp;amp;result_type=mixed&#39;
        + &#39;&amp;amp;callback=JSON_CALLBACK&#39;;
    return $http.jsonp(url).
        success(function(data, status) {
            var lastTweet = data.results[0];
            user = {
                screenName: lastTweet.from_user,
                name: lastTweet.from_user_name,
                image: lastTweet.profile_image_url
            };
        });
});
    
var promise2 = promise1.then(function (value) {
    url = &#39;https://api.twitter.com/1/statuses/user_timeline.json&#39;
        + &#39;?screen_name=&#39;
        + encodeURIComponent(user.screenName)
        + &#39;&amp;amp;callback=JSON_CALLBACK&#39;;
    return $http.jsonp(url).
        success(function(data, status) {
            lastTweets = data;
        });
});

var promiseEnd = promise2.then(function (value) {
    // Success of all the chained requests
    return {user: user, lastTweets: lastTweets};
}, function (reason) {
    // Error in any request
    return $q.reject(reason);
});

return promiseEnd;    
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Cette fois on a un code à plat, on pourrait enchaîner ainsi autant de requêtes qu&#39;on veut sans aucune imbrication. On n&#39;a pas besoin non plus de créer de promise manager avec &lt;i&gt;$q.defer()&lt;/i&gt;. Il y a une première promise déjà résolue, &lt;i&gt;promiseStart&lt;/i&gt;, qui est créé en appelant &lt;i&gt;$q.when()&lt;/i&gt;.&amp;nbsp;Ça n&#39;est pas indispensable, c&#39;est juste pour des raisons de symétrie du code, pour que toutes les étapes aient la même structure que je préfère mettre aussi la première étape dans un &lt;i&gt;then()&lt;/i&gt;. Et chaque callback passé à &lt;i&gt;then()&lt;/i&gt;, qui contient le code d&#39;une étape, avec donc une requête HTTP, renvoie directement la promise de la requête HTTP. Ce qui fait que &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;construit une promise suivante chaînée à celle de la requête HTTP, et qui sera donc résolue quand la réponse HTTP aura été reçue. Mais elle ne sera résolue qu&#39;après exécution du callback enregistré par la méthode &lt;i&gt;success()&lt;/i&gt;, car l&#39;API des promises garantit que les callbacks seront appelés dans leur ordre d&#39;enregistrement. Ici on voit bien que l&#39;ordre est important, si le callback du &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;suivant était appelé avant celui de &lt;i&gt;success()&lt;/i&gt;, ça ne pourrait pas fonctionner. J&#39;ai mis des logs dans le jsFiddle, pour que vous puissiez voir dans la console l&#39;enchaînement des appels.&lt;br /&gt;
&lt;br /&gt;
Tout à la fin, on construit une promise finale, &lt;i&gt;promiseEnd&lt;/i&gt;, en appelant encore &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;sur la promise de la dernière étape, avec un callback de succès qui renvoie la valeur à passer à &lt;i&gt;promiseEnd&lt;/i&gt;&amp;nbsp;s&#39;il n&#39;y a pas eu d&#39;erreur, et un callback d&#39;erreur qui peut traiter une erreur survenue sur n&#39;importe laquelle des requêtes HTTP successives. Et c&#39;est cette &lt;i&gt;promiseEnd&lt;/i&gt; qui est renvoyée par le service, immédiatement et bien sûr non résolue, la résolution de ces différentes promises chaînées se fera en cascade à mesure de l&#39;arrivée des réponses aux requêtes HTTP.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Utilisation des promises dans les vues AngularJS&lt;/h2&gt;
En regardant ces deux exemples, vous avez peut-être remarqué que ce que le contrôleur met dans le scope de la vue, c&#39;est directement la promise renvoyée par le service. Mais qu&#39;elle est utilisée dans la vue comme s&#39;il s&#39;agissait des vraies données. Nous voilà à ce que j&#39;ai indiqué dans la liste tout au début, en point 7 : on peut utiliser les promises comme des données différées dans les vues d&#39;AngularJS.&lt;br /&gt;
&lt;br /&gt;
Le moteur de template d&#39;AngularJS reconnaît les promises, au travers du code suivant du framework :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if (v &amp;amp;&amp;amp; v.then) {
    p = v;
    if (!(&#39;$$v&#39; in v)) {
        p.$$v = undefined;
        p.then(function(val) { p.$$v = val; });
    }
    v = v.$$v;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Si l&#39;objet concerné par un binding a une méthode &lt;i&gt;then()&lt;/i&gt;, ce qui veut dire que c&#39;est une promise, AngularJS appelle sa méthode &lt;i&gt;then()&lt;/i&gt;&amp;nbsp;pour lui attacher un callback qui, lorsque la promise sera résolue en succès, va lui attacher le résultat dans une propriété &lt;i&gt;$$v&lt;/i&gt;. Et le binding se fait en réalité sur cette propriété &lt;i&gt;$$v&lt;/i&gt;. C&#39;est ce mécanisme tout simple qui fait qu&#39;on peut utiliser une promise dans la vue pour n&#39;importe quel binding, pour faire une répétition comme sur l&#39;exemple précédent des tweets, avec des bindings eux-mêmes sur les éléments répétés. Ainsi on ne voit rien dans la vue tant que la promise n&#39;est pas résolue, puisque la propriété &lt;i&gt;$$v&lt;/i&gt;&amp;nbsp;n&#39;existe pas, et tout apparaît comme par magie lors de la résolution de la promise, puisque d&#39;un coup &lt;i&gt;$$v&lt;/i&gt;&amp;nbsp;existe, avec toutes les données à afficher.&lt;br /&gt;
&lt;br /&gt;
Ça marche très bien, et il n&#39;y a même pas besoin de connaître le fonctionnement interne, tant qu&#39;il s&#39;agit de données à afficher dans une vue. Pour faire des modifications, ça marche beaucoup moins bien, la magie a ses limites. Si on met des champs de formulaire avec des directives &lt;i&gt;ng-model&lt;/i&gt; pointant sur les données différées d&#39;une promise, les valeurs saisies par l&#39;utilisateur vont être enregistrées dans l&#39;objet promise lui-même, et non dans le résultat &lt;i&gt;$$v&lt;/i&gt;&amp;nbsp;attaché par AngularJS. A la soumission du formulaire, on aura les valeurs initiales reçues par la promise dans &lt;i&gt;$$v&lt;/i&gt;, et seulement les propriétés modifiées qui auront été recopiées dans la promise. Difficile de faire une mise à jour vers le serveur à partir ce ça. Du coup pour alimenter un formulaire, je déconseille de mettre directement la promise dans le scope. Mais pour de l&#39;affichage ça ne pose aucun problème.&lt;br /&gt;
&lt;br /&gt;
Voilà pour ce tour d&#39;horizon assez complet des possibilités offertes par l&#39;API de promises d&#39;AngularJS, qu&#39;il vaut mieux avoir bien comprises, en particulier si l&#39;on veut écrire un client faisant de nombreux accès à des services HTTP.&amp;nbsp;Ça demande un peu d&#39;habitude pour penser en promises plutôt qu&#39;en imbrications de callbacks, mais à l&#39;usage ça permet de résoudre assez facilement des enchaînements complexes d&#39;opérations asynchrones.</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/6618493494355801381/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2012/12/api-promise-angularjs.html#comment-form' title='12 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/6618493494355801381'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/6618493494355801381'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2012/12/api-promise-angularjs.html' title='L&#39;API Promise d&#39;AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-6817176502993386266</id><published>2012-12-12T11:53:00.003+01:00</published><updated>2012-12-16T17:01:35.769+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="directive"/><category scheme="http://www.blogger.com/atom/ns#" term="pagination"/><title type='text'>Pagination côté client avec une directive AngularJS</title><content type='html'>Dans une application AngularJS, on peut avoir parfois une liste assez importante de données entièrement chargée côté client, et qu&#39;on veut afficher de façon paginée. C&#39;est facile à faire au moyen d&#39;une directive, qui peut être réutilisée pour paginer n&#39;importe quelle liste.&lt;br /&gt;
&lt;br /&gt;
Ça ne veut pas dire qu&#39;il faut systématiquement charger toutes les données et faire la pagination côté client, bien au contraire. Pour de gros volumes notamment, il est beaucoup plus logique de filtrer directement sur le serveur, dans la requête à la base de données, la partie de la liste qui doit être affichée sur la page. Donc n&#39;allez pas répéter que je vous ai dit de toujours faire la pagination côté client. Mais pour les cas où c&#39;est pertinent, voici comment on peut créer une directive réalisant la pagination.&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
Pour afficher une liste, on utilise la directive &lt;i&gt;ngRepeat&lt;/i&gt;&amp;nbsp;standard du framework ; on l&#39;applique en ajoutant un attribut &lt;i&gt;ng-repeat&lt;/i&gt;&amp;nbsp;à une ligne de table, ou un élément &lt;i&gt;&amp;lt;li&amp;gt;&lt;/i&gt;&amp;nbsp;d&#39;une liste.&lt;br /&gt;
&lt;br /&gt;
Le paginateur qu&#39;on va créer aura plusieurs rôles :&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;afficher la page courante, les boutons de navigation entre les pages&lt;/li&gt;
&lt;li&gt;fournir dans le &lt;i&gt;scope&lt;/i&gt;&amp;nbsp;courant une fonction renvoyant les éléments de la page à afficher, à utiliser à la place de la liste complète dans l&#39;attribut &lt;i&gt;ng-repeat&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;span style=&quot;font-style: normal;&quot;&gt;permettre le choix entre plusieurs tailles de page&lt;/span&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h2&gt;
La directive &#39;paginator&#39;&lt;/h2&gt;
&lt;/div&gt;
En voici le code source :
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var paginator = angular.module(&#39;paginator&#39;, []);
paginator.directive(&#39;paginator&#39;, function () {
  var pageSizeLabel = &quot;Page size&quot;;
  return {
    priority: 0,
    restrict: &#39;A&#39;,
    scope: {items: &#39;&amp;amp;&#39;},
    template: 
       &#39;&amp;lt;button ng-disabled=&quot;isFirstPage()&quot; ng-click=&quot;decPage()&quot;&amp;gt;&amp;amp;lt;&amp;lt;/button&amp;gt;&#39;
     + &#39;{{paginator.currentPage+1}}/{{numberOfPages()}}&#39;
     + &#39;&amp;lt;button ng-disabled=&quot;isLastPage()&quot; ng-click=&quot;incPage()&quot;&amp;gt;&amp;amp;gt;&amp;lt;/button&amp;gt;&#39;
     + &#39;&amp;lt;span&amp;gt;&#39; + pageSizeLabel + &#39;&amp;lt;/span&amp;gt;&#39;
     + &#39;&amp;lt;select ng-model=&quot;paginator.pageSize&quot; ng-options=&quot;size for size in pageSizeList&quot;&amp;gt;&amp;lt;/select&amp;gt;&#39;,
    replace: false,
    compile: function compile(tElement, tAttrs, transclude) {
      return {
        pre: function preLink(scope, iElement, iAttrs, controller) {
          scope.pageSizeList = [10, 20, 50, 100];
          scope.paginator = {
            pageSize: 10,
            currentPage: 0
          };

          scope.isFirstPage = function () {
            return scope.paginator.currentPage == 0;
          };
          scope.isLastPage = function () {
            return scope.paginator.currentPage 
                &amp;gt;= scope.items().length / scope.paginator.pageSize - 1;
          };
          scope.incPage = function () {
            if (!scope.isLastPage()) {
              scope.paginator.currentPage++;
            }
          };
          scope.decPage = function () {
            if (!scope.isFirstPage()) {
              scope.paginator.currentPage--;
            }
          };
          scope.firstPage = function () {
            scope.paginator.currentPage = 0;
          };
          scope.numberOfPages = function () {
            return Math.ceil(scope.items().length / scope.paginator.pageSize);
          };
          scope.$watch(&#39;paginator.pageSize&#39;, function(newValue, oldValue) { 
            if (newValue != oldValue) {
              scope.firstPage();
            } 
          });

          // ---- Functions available in parent scope -----

          scope.$parent.firstPage = function () {
            scope.firstPage();
          };
          // Function that returns the reduced items list, to use in ng-repeat
          scope.$parent.pageItems = function () {
            var start = scope.paginator.currentPage * scope.paginator.pageSize;
            var limit = scope.paginator.pageSize;
            return scope.items().slice(start, start + limit); 
          };
        },
        post: function postLink(scope, iElement, iAttrs, controller) {}
      };
    }
  };
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Le template qui est en dur dans la directive peut être mis dans un fichier HTML séparé pour un peu plus de lisibilité, et chargé grâce à la propriété &lt;i&gt;templateUrl&lt;/i&gt;, au prix d&#39;une portabilité un peu moins pratique puisqu&#39;on se retrouve avec deux fichiers au lieu d&#39;un seul. Là ce n&#39;est pas vraiment critique puisqu&#39;il se limite à 5 lignes assez simples.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Comment on l&#39;utilise&lt;/h2&gt;
Vous avez &lt;a href=&quot;http://jsfiddle.net/tchatel/8LwGE/&quot; target=&quot;_blank&quot;&gt;ici un jsFiddle&lt;/a&gt; complet montrant l&#39;utilisation de cette directive. Le principe est simple, il suffit d&#39;ajouter la directive sous la forme d&#39;un attribut&lt;i&gt;&amp;nbsp;paginator&lt;/i&gt;&amp;nbsp;à l&#39;élément HTML dans lequel doivent être générés les contrôles de navigation, comme ceci :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;span paginator items=&quot;numbers | filter:search&quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Le second attribut,&amp;nbsp;&lt;i&gt;items&lt;/i&gt;, correspond à la liste complète avant pagination, telle qu&#39;on l&#39;aurait spécifiée dans un &lt;i&gt;ng-repeat&lt;/i&gt;, et elle peut être filtrée ou ordonnée sans problème. C&#39;est cette liste fournie dans l&#39;attribut &lt;i&gt;items&lt;/i&gt;&amp;nbsp;qui est la source pour le paginateur. En fait c&#39;est exactement l&#39;expression qui était utilisée comme liste dans l&#39;attribut &lt;i&gt;ng-repeat&lt;/i&gt;&amp;nbsp;qui est déplacée ici.&lt;br /&gt;
&lt;br /&gt;
Et le paginateur fournit dans le&amp;nbsp;&lt;i&gt;scope&amp;nbsp;&lt;/i&gt;une fonction&amp;nbsp;&lt;i&gt;pageItems()&lt;/i&gt;&amp;nbsp;renvoyant uniquement les éléments de la page courante.&amp;nbsp;Du coup dans l&#39;attribut &lt;i&gt;ng-repeat&lt;/i&gt;, on boucle sur&amp;nbsp;&lt;i&gt;pageItems()&lt;/i&gt; directement, sans faire ici ni filtrage ni tri :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;tr ng-repeat=&quot;number in pageItems()&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Il n&#39;y a rien de plus à faire pour mettre en place avec cette directive la pagination côté client. Enfin presque rien. Quand l&#39;utilisateur tape quelque chose dans la zone de recherche, la liste d&#39;origine filtrée n&#39;est plus la même, du coup la page courante n&#39;a plus de sens. Pour revenir à la première page dès qu&#39;on modifie la recherche, on ajoute un &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;dans le contrôleur, qui utilise la fonction &lt;i&gt;firstPage()&lt;/i&gt;&amp;nbsp;publiée par la directive dans le &lt;i&gt;scope &lt;/i&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$scope.$watch(&#39;search&#39;, function(newValue, oldValue) { 
    if (newValue != oldValue) {
        $scope.firstPage();
    } 
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
On pourrait bien sûr préférer que ce soit géré automatiquement dans la directive, ça serait plus simple. Mais il n&#39;est pas possible de faire un &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;sur un tableau, ni sur un objet complexe. Un&amp;nbsp;&lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;n&#39;est possible que sur un type simple, chaîne de caractères, nombre ou booléen. Du coup si l&#39;on voulait dans la directive savoir quand la liste d&#39;origine est modifiée, pour revenir à la première page, il faudrait faire un &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;sur un hashage du tableau, lequel hashage devrait être recalculé par AngularJS à chaque modification. C&#39;est théoriquement possible, mais potentiellement très coûteux si le tableau est un peu gros. Donc il vaut mieux en terme de performances déclencher manuellement dans le contrôleur le retour à la première page si l&#39;utilisateur change le filtrage, ou l&#39;ordre de tri. D&#39;une façon générale, faire un calcul complexe dans un &lt;i&gt;$watch&lt;/i&gt;&amp;nbsp;n&#39;est jamais une bonne idée.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Comment ça marche&lt;/h2&gt;
Regardons de plus près le code de la directive. C&#39;est une directive qui ne peut être appliquée que comme un attribut HTML (&lt;i&gt;restrict: &#39;A&#39;&lt;/i&gt;). Le template fourni sert à générer toute une portion de HTML à l&#39;intérieur de l&#39;élément sur lequel est placé l&#39;attribut &lt;i&gt;paginator&lt;/i&gt;, c&#39;est ce qu&#39;indique le &lt;i&gt;replace: false&lt;/i&gt;. Avec &lt;i&gt;replace: true&lt;/i&gt;, l&#39;élément lui-même aurait été remplacé par le template.&lt;br /&gt;
&lt;br /&gt;
La propriété &lt;i&gt;scope&lt;/i&gt;&amp;nbsp;est la plus complexe. Le fait de mettre comme valeur un objet entre accolades crée un scope isolé pour la directive, pour que les propriétés et fonctions publiées dans le scope par la directive n&#39;aient pas d&#39;impact sur le scope extérieur lié à l&#39;emplacement de l&#39;élément HTML dans la vue. Le scope est dit isolé car il n&#39;a pas pour prototype JavaScript son scope parent, ce qui évite les effets de bord indésirables. Mais son scope parent est tout de même accessible, en utilisant explicitement sa propriété $parent. C&#39;est ce qui est utilisé à la fin du code de la directive, pour publier les deux fonctions &lt;i&gt;firstPage()&lt;/i&gt;&amp;nbsp;et &lt;i&gt;pageItems()&lt;/i&gt;&amp;nbsp;dans le scope parent, puisqu&#39;elles sont destinées à être utilisées dans la vue et le contrôleur, donc en dehors de la directive :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;scope.$parent.firstPage = function () {
    scope.firstPage();
};
// Function that returns the reduced items list, to use in ng-repeat
scope.$parent.pageItems = function () {
    var start = scope.paginator.currentPage * scope.paginator.pageSize;
    var limit = scope.paginator.pageSize;
    return scope.items().slice(start, start + limit); 
};&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
La propriété &lt;i&gt;scope&lt;/i&gt;&amp;nbsp;de la directive est égale à&amp;nbsp;&lt;i&gt;{items: &#39;&amp;amp;&#39;}&lt;/i&gt;.&amp;nbsp;Ça a pour effet de publier dans le scope isolé une propriété &lt;i&gt;items&lt;/i&gt;, et le signe &lt;i&gt;&amp;amp;&lt;/i&gt;&amp;nbsp;indique que sa valeur est obtenue en exécutant l&#39;expression contenue dans l&#39;attribut de même nom de l&#39;élément HTML. On pourrait expliciter le nom de l&#39;attribut si on voulait avoir un nom de propriété publié dans le scope isolé qui diffère de celui de l&#39;attribut, par exemple&amp;nbsp;&lt;i&gt;{listItems: &#39;&amp;amp;items&#39;}&lt;/i&gt;, mais si rien n&#39;est indiqué ça prend l&#39;attribut de même nom. C&#39;est donc cette seule indication &lt;i&gt;scope: {items: &#39;&amp;amp;&#39;} &lt;/i&gt;qui crée un scope isolé et qui y publie la liste d&#39;objets correspondant à l&#39;expression qu&#39;on a mise dans l&#39;attribut &lt;i&gt;items&lt;/i&gt;&amp;nbsp;de l&#39;élément HTML.&lt;br /&gt;
&lt;br /&gt;
Il n&#39;y a pas vraiment de subtilités dans le reste du code, c&#39;est le même code qu&#39;on écrirait dans un contrôleur correspondant à la portion de vue qui se trouve dans le template. On publie dans le scope diverses méthodes pour calculer le nombre de pages, passer à la page précédente ou suivante, savoir si on est sur la première ou la dernière page, etc. Le &lt;i&gt;watch&lt;/i&gt;&amp;nbsp;sert à revenir automatiquement à la première page lorsque l&#39;utilisateur change la taille de la page.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Mise en cache de l&#39;état de la pagination&lt;/h2&gt;
La directive marche bien ainsi, mais on peut vouloir la perfectionner un peu. Car si l&#39;on change de vue, puis on revient sur la vue contenant la liste paginée, on perd la taille de page ainsi que la page courante. Essayez dans le jsFiddle, j&#39;ai mis une seconde vue vide juste pour montrer cet effet. Bon c&#39;est normal, la page courante et la taille de page sont juste des propriétés du scope de la directive, qui est recréé à chaque fois qu&#39;on revient sur la vue. Mais ce n&#39;est pas très pratique : imaginons qu&#39;on ouvre une vue de détails à partir d&#39;un élément de la liste paginée, ça serait quand même mieux si en revenant sur la liste paginée on la retrouvait dans le même état.&lt;br /&gt;
&lt;br /&gt;
On peut conserver l&#39;état de la pagination dans un cache, en utilisant le service standard &lt;i&gt;$cacheFactory&lt;/i&gt;&amp;nbsp;d&#39;AngularJS. En fait comme son nom l&#39;indique ce service est une factory destinée à créer un service de cache en mémoire. Donc on peut ajouter un service à notre module contenant déjà la directive :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;paginator.factory(&#39;PageStateCache&#39;, [&#39;$cacheFactory&#39;, function ($cacheFactory) {
    return $cacheFactory(&#39;PageStateCache&#39;);
}]);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Ce service &lt;i&gt;PageStateCache&lt;/i&gt;&amp;nbsp;est alors utilisé dans la directive pour conserver en cache l&#39;état de la pagination, et pour le retrouver dans le cache s&#39;il y a été sauvegardé.&lt;br /&gt;
&lt;br /&gt;
Voici ce que ça donne (avec un &lt;a href=&quot;http://jsfiddle.net/tchatel/K9mcP/&quot; target=&quot;_blank&quot;&gt;nouveau jsFiddle complet ici&lt;/a&gt;) :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;compile: function compile(tElement, tAttrs, transclude) {
  var cacheId = tAttrs.cache ? tAttrs.cache + &#39;.paginator&#39; : &#39;&#39;;
  return {
    pre: function preLink(scope, iElement, iAttrs, controller) {
      scope.pageSizeList = [10, 20, 50, 100];
      var defaultSettings = {
        pageSize: 10,
        currentPage: 0
      };
      scope.paginator = cacheId 
          ? PageStateCache.get(cacheId) || defaultSettings 
          : defaultSettings;
      if (cacheId) {
        PageStateCache.put(cacheId, scope.paginator)
      }&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Un identifiant est construit à partir d&#39;une chaîne de caractères spécifiée dans l&#39;attribut &lt;i&gt;cache&lt;/i&gt;&amp;nbsp;du même élément HTML, afin qu&#39;il n&#39;y ait pas de collisions entre les caches de différentes listes paginées. Cet identifiant est utilisé pour stocker dans le cache l&#39;objet &lt;i&gt;scope.paginator&lt;/i&gt;, et pour le retrouver dans le cache quand on arrive sur la page. Du coup les valeurs par défaut en dur ne sont plus utilisées que lorsqu&#39;on ne trouve rien dans le cache, c&#39;est-à-dire lors du premier affichage de la liste paginée.&lt;br /&gt;
&lt;br /&gt;
Et dans la vue, il y a simplement cet attribut &lt;i&gt;cache&lt;/i&gt;&amp;nbsp;supplémentaire pour former un identifiant unique pour cette liste paginée :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;span paginator items=&quot;numbers | filter:search&quot; cache=&quot;numbers&quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Vous pouvez essayer maintenant dans le jsFiddle de changer de vue, cette fois quand on revient sur la vue contenant la liste paginée, on la retrouve bien dans l&#39;état où elle était.&lt;br /&gt;
&lt;br /&gt;
Voilà, vous savez maintenant comment faire facilement de la pagination côté client avec AngularJS.&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/6817176502993386266/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2012/12/pagination-cote-client-directive-angularjs.html#comment-form' title='5 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/6817176502993386266'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/6817176502993386266'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2012/12/pagination-cote-client-directive-angularjs.html' title='Pagination côté client avec une directive AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2500483166927903911.post-872185000467356933</id><published>2012-12-11T20:35:00.000+01:00</published><updated>2012-12-16T17:04:08.882+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="injection de dépendances"/><category scheme="http://www.blogger.com/atom/ns#" term="provider"/><category scheme="http://www.blogger.com/atom/ns#" term="service"/><title type='text'>Décoration de services AngularJS</title><content type='html'>C&#39;est bientôt Noël, mais il ne s&#39;agit pas d&#39;accrocher des guirlandes aux services d&#39;une application AngularJS. Cherchez plutôt du côté du design pattern &lt;i&gt;Decorator&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
Pour compléter&amp;nbsp;&lt;a href=&quot;http://www.frangular.com/2012/12/differentes-facons-de-creer-un-service-angularjs.html&quot;&gt;l&#39;article précédent&lt;/a&gt;&amp;nbsp;sur les différentes façons de créer un service, il existe aussi une technique dans AngularJS pour modifier un service existant lors de son instanciation, ou pour substituer à l&#39;instance du service un objet différent mais similaire.&lt;br /&gt;
&lt;br /&gt;
Cette fois ce n&#39;est pas via une méthode de l&#39;objet module. La méthode &lt;i&gt;decorator()&lt;/i&gt;&amp;nbsp;à utiliser se trouve dans le service standard &lt;i&gt;$provide&lt;/i&gt;, qu&#39;il faut bien sûr injecter. On peut appeler cette méthode&amp;nbsp;dans la méthode &lt;i&gt;config&lt;/i&gt;&amp;nbsp;du module principal de l&#39;application, ou d&#39;un autre module.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Elle ne crée pas un nouveau service, elle permet de modifier ou remplacer&amp;nbsp;un service déjà référencé. Il faut donc pour pouvoir appeler cette méthode&amp;nbsp;&lt;i&gt;$provide.&lt;b&gt;decorator&lt;/b&gt;(name, decorator)&lt;/i&gt;, avoir créé avant un service qui a précisément ce nom-là. Plus précisément, AngularJS va modifier la méthode &lt;i&gt;$get&amp;nbsp;&lt;/i&gt;du provider enregistré sous le nom passé comme premier paramètre suffixé par &#39;Provider&#39;, comme on le voit dans le code du framework :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;function decorator(serviceName, decorFn) {
    var origProvider = providerInjector.get(serviceName + providerSuffix),
        orig$get = origProvider.$get;

    origProvider.$get = function() {
        var origInstance = instanceInjector.invoke(orig$get, origProvider);
        return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Ca veut dire que&amp;nbsp;&lt;i&gt;decorator()&lt;/i&gt;&amp;nbsp;ne peut être appelé que si un service de ce nom là a déjà été créé dans un module par l&#39;une de ses méthodes &lt;i&gt;provider()&lt;/i&gt;,&amp;nbsp;&lt;i&gt;factory()&lt;/i&gt;,&amp;nbsp;&lt;i&gt;service()&lt;/i&gt;&amp;nbsp;ou&amp;nbsp;&lt;i&gt;value()&lt;/i&gt;. Mais pas par la méthode&amp;nbsp;&lt;i&gt;constant() &lt;/i&gt;! Si vous avez lu attentivement le post précédent, vous aurez retenu que la méthode&amp;nbsp;&lt;i&gt;constant()&lt;/i&gt;&amp;nbsp;est la seule qui ne crée pas un vrai provider, car il n&#39;a pas de méthode&amp;nbsp;&lt;i&gt;$get&lt;/i&gt;. La conséquence est que les services créés avec la méthode&lt;i&gt;&amp;nbsp;constant()&lt;/i&gt;&amp;nbsp;ne peuvent pas être décorés.&lt;br /&gt;
&lt;br /&gt;
Lors du premier accès au service, la fonction passée à&amp;nbsp;&lt;i&gt;decorator()&lt;/i&gt;&amp;nbsp;se voit injecter comme un paramètre &lt;i&gt;$delegate&lt;/i&gt; l&#39;instance du service d&#39;origine fraîchement créé par son provider d&#39;origine. Elle peut soit la modifier, soit créer une nouvelle instance qui s&#39;appuie sur l&#39;ancienne. Et elle renvoie l&#39;objet qui sera réellement publié comme instance du service, qu&#39;il s&#39;agisse de la &amp;nbsp;même instance modifiée ou d&#39;un nouvel objet.&lt;br /&gt;
&lt;br /&gt;
Bien, mais à quoi ça sert ? Car on pourrait directement créer le service correspondant à ce qu&#39;on veut... sauf si on n&#39;a pas la main sur le service à modifier. Ça peut être un service standard du framework AngularJS, ou encore un service utilisé dans d&#39;autres applications et dont on ne veut pas modifier le code source. L&#39;usage est celui du design pattern &lt;i&gt;Decorator&lt;/i&gt;, on va pouvoir dans la configuration d&#39;une application faire un tour de passe-passe consistant à modifier le comportement d&#39;un service sans que les autres services qui l&#39;utilisent ne s&#39;en rendent compte, et sans aucune modification du code source du service d&#39;origine.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple de code - parfaitement inutile mais c&#39;est juste pour montrer la syntaxe - où le service standard &lt;i&gt;$log&lt;/i&gt;&amp;nbsp;est remplacé par un log décoré (&lt;a href=&quot;http://jsfiddle.net/tchatel/dYSwL/&quot; target=&quot;_blank&quot;&gt;jsFiddle ici&lt;/a&gt;) :&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var app = angular.module(&#39;app&#39;, []);
app.config([&#39;$provide&#39;, function($provide) {
    $provide.decorator(&#39;$log&#39;, [&#39;$delegate&#39;, function ($delegate) {
        return {
            error: function (text) { $delegate.error(&quot;***&quot; + text + &quot;***&quot;); },
            info: function (text) { $delegate.info(&quot;***&quot; + text + &quot;***&quot;); },
            log: function (text) { $delegate.info(&quot;***&quot; + text + &quot;***&quot;); },
            warn: function (text) { $delegate.info(&quot;***&quot; + text + &quot;***&quot;); },
        };
    }]);
}]);

function Ctrl($scope, $log) {
    $log.info(&#39;ok&#39;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.frangular.com/feeds/872185000467356933/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.frangular.com/2012/12/decoration-de-services-angularjs.html#comment-form' title='3 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/872185000467356933'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2500483166927903911/posts/default/872185000467356933'/><link rel='alternate' type='text/html' href='http://www.frangular.com/2012/12/decoration-de-services-angularjs.html' title='Décoration de services AngularJS'/><author><name>arzeztret</name><uri>http://www.blogger.com/profile/10022463007390261438</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry></feed>