{"id":274,"date":"2015-02-04T15:38:27","date_gmt":"2015-02-04T14:38:27","guid":{"rendered":"http:\/\/lalloue.fr\/blog\/?p=274"},"modified":"2015-03-02T18:05:00","modified_gmt":"2015-03-02T17:05:00","slug":"afficher-un-loader-pendant-une-requete-ajax","status":"publish","type":"post","link":"http:\/\/lalloue.fr\/blog\/afficher-un-loader-pendant-une-requete-ajax\/","title":{"rendered":"Afficher un loader pendant une requ\u00eate Ajax"},"content":{"rendered":"<p><a href=\"http:\/\/lalloue.fr\/blog\/wp-content\/uploads\/2014\/12\/AngularJS-large.png\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-medium wp-image-166\" src=\"http:\/\/lalloue.fr\/blog\/wp-content\/uploads\/2014\/12\/AngularJS-large-300x84.png\" alt=\"AngularJS-large\" width=\"300\" height=\"84\" srcset=\"http:\/\/lalloue.fr\/blog\/wp-content\/uploads\/2014\/12\/AngularJS-large-300x84.png 300w, http:\/\/lalloue.fr\/blog\/wp-content\/uploads\/2014\/12\/AngularJS-large.png 383w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Le probl\u00e8me avec les requ\u00eates Ajax c&#8217;est qu&#8217;on ne sait jamais vraiment quand on va recevoir la r\u00e9ponse.<br \/>\nEn principe c&#8217;est rapide mais pour le si peu que la connexion est lente, que le serveur rame un peu \u00e0 ce moment l\u00e0, ou pire qu&#8217;il est en maintenance, le temps de r\u00e9ponse peut passer de 1 secondes \u00e0 un temps ind\u00e9fini.<\/p>\n<p>Et pendant ce temps l&#8217;utilisateur ne sait pas ce qui se passe : d&#8217;o\u00f9 l\u2019int\u00e9r\u00eat d&#8217;int\u00e9grer un loader (ou mire de chargement) \u00e0 votre page pendant l\u2019ex\u00e9cution de votre requ\u00eate.<br \/>\nVoici un petit exemple pour le mettre en place :<\/p>\n<p><!--more--><\/p>\n<p>&nbsp;<\/p>\n<h3>Introduction :<\/h3>\n<p>Tout commence par l&#8217;envoi d&#8217;une requ\u00eate. Cela se fait via la m\u00e9thode <strong><span style=\"color: #2db3e7;\">$http<\/span><\/strong> d&#8217;AngularJs comme l&#8217;exemple ci-dessous :<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\napp.controller('MonController', ['$scope', '$http',\r\n               function($scope, $http) {\r\n\r\n\tvar url = 'MON_URL';\r\n\r\n\t$http.get(url).then(function success(response) {\r\n\t\t\/\/ Return result\r\n\t\treturn response.data.result;\r\n\t}, function error(reason) {\r\n\t\treturn false;\r\n\t});\r\n\r\n]);\r\n<\/pre>\n<p>Si la r\u00e9ponse est correcte on passe donc dans la m\u00e9thode <em>&#8220;success&#8221;, <\/em>sinon, dans la m\u00e9thode <em>&#8220;error&#8221;<\/em>.<\/p>\n<p>On veut donc maintenant lancer le loader avant que la requ\u00eate soit envoy\u00e9e, et cacher le loader une fois la r\u00e9ponse r\u00e9cup\u00e9r\u00e9e.<\/p>\n<p>&nbsp;<\/p>\n<h3>D\u00e9j\u00e0, comment on affiche le loader ?<\/h3>\n<p>Pour \u00e7a je vous conseille la petite librairie &#8216;spinner&#8217; qui fonctionne tr\u00e8s bien (<a href=\"http:\/\/fgnass.github.io\/spin.js\/\">spinner.js<\/a>).<br \/>\nIl existe d&#8217;autres solutions mais celle-ci \u00e0 l&#8217;avantage d&#8217;\u00eatre l\u00e9g\u00e8re et de n&#8217;avoir pas n\u00e9cessairement besoin d&#8217;inclure JQuery pour l&#8217;utiliser.<\/p>\n<p>Pour l&#8217;utiliser vous avez une documentation au lien que je vous ai donn\u00e9 ci-dessus, mais vu que je suis d&#8217;humeur sympathique je vais vous faire un petit r\u00e9sum\u00e9 en fran\u00e7ais ici :<\/p>\n<h5>Installation :<\/h5>\n<p>Il vous suffit de r\u00e9cup\u00e9rer le fichier &#8220;spin.js&#8221; et de l&#8217;ajouter \u00e0\u00a0votre projet.<br \/>\nPuis ensuite il sera n\u00e9cessaire de l&#8217;inclure dans votre page html tel que ci-dessous :<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;!doctype html&gt;\r\n&lt;html&gt;\r\n&lt;head&gt;\r\n&lt;script type=&quot;text\/javascript&quot; src=&quot;spin.js&quot;&gt;&lt;\/script&gt;\r\n\r\n&lt;\/head&gt;\r\n...\r\n<\/pre>\n<p>Si vous utilisez <em>bower<\/em>, une petite ligne de commande et le tour sera jou\u00e9 :<\/p>\n<pre lang=\"bash\">$ bower install spin.js --save<\/pre>\n<h5>Utilisation :<\/h5>\n<p>Sur ma balise &#8216;body&#8217; de index.html j&#8217;apelle une m\u00e9thode &#8216;onLoad()&#8217;<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;!doctype html&gt;\r\n&lt;html&gt;\r\n\t&lt;head&gt;\r\n\t&lt;\/head&gt;\r\n\t&lt;body onload=&quot;onLoad()&quot;&gt;\r\n\t\t&lt;!-- CONTENT --&gt;\r\n\t\t&lt;div ng-view&gt;&lt;\/div&gt;\r\n\r\n\t\t&lt;!-- LOADING SPINNER --&gt;\r\n\t\t&lt;div id=&quot;loading-ws-bar&quot;&gt;&lt;\/div&gt;\r\n\t\t&lt;div id=&quot;loading-ws-background-page&quot;&gt;&lt;\/div&gt;\r\n\t&lt;\/body&gt;\r\n...\r\n<\/pre>\n<p>que je d\u00e9finie dans mon &#8216;<span style=\"color: #2db3e7;\">main.js<\/span>&#8216; :<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction onLoad() {\r\n\tloaderUtils.generateLoading('loading-ws-bar', ['#157199','#1B91C4']);\r\n}\r\n<\/pre>\n<p>Et il ne vous manque plus que la fameuse classe &#8216;<span style=\"color: #2db3e7;\">loaderUtils<\/span>&#8216; pour finir d&#8217;int\u00e9grer votre magnifique spinner.<br \/>\nJe vous donne la classe enti\u00e8re parce que vous verrez qu&#8217;on va r\u00e9utiliser les m\u00e9thodes dans la suite :<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n'use strict';\r\n\r\nfunction LoaderUtils() {\r\n}\r\n\r\n\/**\r\n * Generate the spin loader\r\n *\/\r\nLoaderUtils.prototype.generateLoading = function(domElementId, spinnerColor) {\r\n\r\n\tvar opts = {\r\n\t\tlines : 12, \/\/ The number of lines to draw\r\n\t\tlength : 15, \/\/ The length of each line\r\n\t\twidth : 15, \/\/ The line thickness\r\n\t\tradius : 30, \/\/ The radius of the inner circle\r\n\t\tcorners : 1, \/\/ Corner roundness (0..1)\r\n\t\trotate : 0, \/\/ The rotation offset\r\n\t\tdirection : 1, \/\/ 1: clockwise, -1: counterclockwise\r\n\t\tcolor : spinnerColor, \/\/ #rgb or #rrggbb or array of colors\r\n\t\tspeed : 1, \/\/ Rounds per second\r\n\t\ttrail : 90, \/\/ Afterglow percentage\r\n\t\tshadow : true, \/\/ Whether to render a shadow\r\n\t\thwaccel : true, \/\/ Whether to use hardware acceleration\r\n\t\tclassName : 'spinner', \/\/ The CSS class to assign to the spinner\r\n\t\tzIndex : 2e9, \/\/ The z-index (defaults to 2000000000)\r\n\t\ttop : '50%', \/\/ Top position relative to parent in px\r\n\t\tleft : '50%' \/\/ Left position relative to parent in px\r\n\t};\r\n\r\n\tnew Spinner(opts).spin(document.getElementById(domElementId));\r\n};\r\n\r\n\/**\r\n * show the loader\r\n *\/\r\nLoaderUtils.prototype.showLoader = function() {\r\n\t$('#loading-ws-bar, #loading-ws-background-page').show();\r\n};\r\n\r\n\/**\r\n * hide the loader\r\n *\/\r\nLoaderUtils.prototype.hideLoader = function() {\r\n\t$('#loading-ws-bar, #loading-ws-background-page').hide();\r\n};\r\n\r\nvar loaderUtils = new LoaderUtils();\r\n<\/pre>\n<p>Bien-sur toutes les propri\u00e9t\u00e9s du spinner sont personnalisables. Pour plus d&#8217;infos regardez les commentaires en bout de ligne de l&#8217;objet &#8216;<span style=\"color: #2db3e7;\">opts<\/span>&#8216;.<\/p>\n<p>Et pour finir, voici un petit css \u00e0 int\u00e9grer pour un meilleur affichage, mais l\u00e0 pareil, tout le monde peut personnaliser comme il l&#8217;entend :<\/p>\n<pre class=\"brush: css; title: ; notranslate\" title=\"\">\r\n\/* \tSpinner css stylesheet\r\n\tSee 'loaderUtils.js' to know how to use it\r\n*\/\r\n\r\n\/* Transparent background color to disable action on page *\/\r\n#loading-ws-background-page {\r\n    background-color: rgba(0, 0, 0, 0.5);\r\n    bottom: 0;\r\n    display:none;\r\n    left: 0;\r\n    position: fixed;\r\n    right: 0;\r\n    top: 0;\r\n    z-index: 10000;\r\n}\r\n#loading-ws-bar {\r\n    border-radius: 2px;\r\n\tdisplay:none;\r\n    height: 100px;\r\n    left: 50%;\r\n    margin-left: -75px;\r\n    margin-top: -50px;\r\n    position: fixed;\r\n    top: 50%;\r\n    width: 150px;\r\n    z-index: 10001;\r\n}\r\n<\/pre>\n<p>Mais comme vous avez mis par d\u00e9faut un &#8216;<span style=\"color: #2db3e7;\">display:none<\/span>&#8216; dans votre css le spinner ne s&#8217;affichera jamais. C&#8217;est normal !<br \/>\nOn va l&#8217;afficher seulement quand on enverra une requ\u00eate, donc on va le faire c\u00f4t\u00e9 javascript.<\/p>\n<p>&nbsp;<\/p>\n<h3>Afficher le loader \u00e0 chaque requ\u00eate Ajax :<\/h3>\n<p>Maintenant qu&#8217;on a int\u00e9gr\u00e9 &#8216;<span style=\"color: #2db3e7;\">spin.js<\/span>&#8216; et qu&#8217;on l&#8217;a param\u00e9tr\u00e9, rien de plus simple pour l&#8217;afficher vous allez voir.<br \/>\nOn reprend donc notre exemple d&#8217;appel Ajax du d\u00e9but et on y ajoute 3 petits bouts de code :<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\napp.controller('MonController', ['$scope', '$http',\r\n               function($scope, $http) {\r\n\r\n\tvar url = 'MON_URL';\r\n\r\n\t\/\/ Display loader\r\n\tloaderUtils.showLoader();\r\n\r\n\t$http.get(url).then(function success(response) {\r\n\t\t\/\/ Display loader\r\n\t\tloaderUtils.hideLoader();\r\n\t\t\/\/ Return result\r\n\t\treturn response.data.result;\r\n\t}, function error(reason) {\r\n\t\t\/\/ Display loader\r\n\t\tloaderUtils.hideLoader();\r\n\t\treturn false;\r\n\t});\r\n\r\n]);\r\n<\/pre>\n<p>Vous voyez donc qu&#8217;on \u00e0 ajout\u00e9 un appel \u00e0 la classe &#8216;<span style=\"color: #2db3e7;\">loaderUtils<\/span>&#8216; pour afficher et cacher le spinner en fonction de l&#8217;\u00e9tat de la requ\u00eate.<br \/>\nEt c&#8217;est tout !<br \/>\nIl vous suffira maintenant d&#8217;appeler les m\u00e9thodes &#8216;<span style=\"color: #2db3e7;\">showLoader<\/span>&#8216; et &#8216;<span style=\"color: #2db3e7;\">hideLoader<\/span>&#8216; \u00e0 chaque requ\u00eate Ajax pour afficher votre loader.<\/p>\n<p>&nbsp;<\/p>\n<h3>Utilisation un peu plus pouss\u00e9e :<\/h3>\n<p>Mais j&#8217;entend d\u00e9j\u00e0 quelques uns dire : <em>&#8220;C&#8217;est sympas ton truc, mais faut le faire \u00e0 chaque requ\u00eate du coup !&#8221;<\/em>.<\/p>\n<p>Et bien OUI, la m\u00e9thode expliqu\u00e9e ci-dessus oblige \u00e0 int\u00e9grer les m\u00e9thodes d&#8217;affichage et pour cacher le loader avant et apr\u00e8s CHAQUE requ\u00eate. Et c&#8217;est p\u00e9nible.<br \/>\nAlors est-ce qu&#8217;il existe une mani\u00e8re de le faire de fa\u00e7on transparente \u00e0 chaque requ\u00eate syst\u00e9matiquement?<\/p>\n<p>OUI !<\/p>\n<p>Cette technique est un peu plus pouss\u00e9e, mais je vais vous expliquer comment faire :<\/p>\n<p>Pour cela on va <span style=\"color: #000000;\"><strong>intercepter les requ\u00eates<\/strong><\/span> avec un &#8216;interceptor&#8217;.<br \/>\nUn interceptor est une factory que l\u2019on va attacher au $httpProvider. De cette mani\u00e8re on va pouvoir intercepter les requ\u00eates faites via le service $http.<br \/>\nSi vous voulez un peu plus de documentation vous pouvez lire <a href=\"https:\/\/docs.angularjs.org\/api\/ng\/service\/$http#interceptors\">la doc officielle du service $http ici<\/a>.<\/p>\n<h5>Mise en place :<\/h5>\n<p>On va d&#8217;abord indiquer au &#8216;<span style=\"color: #2db3e7;\">$httpProvider<\/span>&#8216; qu&#8217;on lui assigne un intercepteur. Pour cela j&#8217;ajoute les lignes suivantes dans mon &#8216;<span style=\"color: #2db3e7;\">app.js<\/span>&#8216; :<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\napp.config(['$httpProvider', function($httpProvider) {\r\n    $httpProvider.interceptors.push('httpInterceptor');\r\n}]);\r\n<\/pre>\n<p>Ainsi toutes les requ\u00eates http faites par le service &#8216;<span style=\"color: #2db3e7;\">$http<\/span>&#8216; seront intercept\u00e9es par notre service &#8216;<span style=\"color: #2db3e7;\">httpInterceptor<\/span>&#8216;.<br \/>\nEt ensuite on va cr\u00e9er le fameux httpInterceptor.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\napp.factory('HttpInterceptor', ['$q', function($q) {\r\n\treturn {\r\n                \/\/ M\u00e9thode appel\u00e9e avant une requ\u00eate http\r\n\t\trequest : function(config) {\r\n\t\t\treturn config || $q.when(config);\r\n\t\t},\r\n                \/\/ M\u00e9thode appel\u00e9e lors d'une r\u00e9ponse avec succ\u00e8s de la requ\u00eate\r\n\t\tresponse : function(response) {\r\n\t\t\treturn response || $q.when(response);\r\n\t\t},\r\n                \/\/ M\u00e9thode appel\u00e9e lors d'une r\u00e9ponse en erreur de la requ\u00eate\r\n\t\tresponseError : function(reason) {\r\n\t\t\treturn $q.reject(reason);\r\n\t\t},\r\n\t};\r\n}]);\r\n<\/pre>\n<h5>Et le loader dans tout \u00e7a ?<\/h5>\n<p>Qu&#8217;est-ce qu&#8217;on fait exactement ici?<\/p>\n<p>On reprend les m\u00e9thodes du &#8216;<span style=\"color: #2db3e7;\">$httpProvider<\/span>&#8216; appel\u00e9es et on les intercepte pour les r\u00e9\u00e9crire. On a donc ici 3 m\u00e9thodes :<\/p>\n<ul>\n<li><strong>request<\/strong> : m\u00e9thode appel\u00e9e avant que la requ\u00eate Ajax soit ex\u00e9cut\u00e9e.<\/li>\n<li><strong>response<\/strong> : m\u00e9thode appel\u00e9e lorsqu&#8217;on re\u00e7oit la r\u00e9ponse de la requ\u00eate quand elle s&#8217;est bien d\u00e9roul\u00e9e.<\/li>\n<li><strong>responseError<\/strong> : m\u00e9thode appel\u00e9e lorsqu&#8217;on re\u00e7oit une r\u00e9ponse en erreur de la requ\u00eate envoy\u00e9e.<\/li>\n<\/ul>\n<p>Tel que d\u00e9crit ci-dessus, je retourne les m\u00eames informations que celles r\u00e9ceptionn\u00e9es, donc l&#8217;intercepteur ne sert \u00e0 rien dans le cas pr\u00e9sent.<br \/>\nMais si vous voyez \u00e0 peu pr\u00e8s ou je veux aller, on va pouvoir utiliser ces m\u00e9thodes pour appeler notre fameux loader !<\/p>\n<p>Ainsi, ce sera le httpInterceptor qui se chargera d&#8217;afficher ou de cacher le\u00a0spinner et tout \u00e7a de fa\u00e7on transparente pour les controlleurs.<br \/>\nIl suffit donc d\u2019appeler le &#8216;showLoader&#8217; depuis la m\u00e9thode &#8216;request&#8217; et le &#8216;hideLoader&#8217; depuis les m\u00e9thodes &#8216;response&#8217; et &#8216;responseError&#8217;.<\/p>\n<h5>Et c&#8217;est tout?<\/h5>\n<p>Et bien presque, mais\u00a0avant de vous donner le code d\u00e9finitif, j&#8217;attire votre attention sur un petit point important :<\/p>\n<p>Si vous lancez <strong>plusieurs requ\u00eates \u00e0 la suite<\/strong> que va-t-il se passer?<\/p>\n<p>Et bien si on fait \u00a0comme je l&#8217;ai dis juste avant, la 1\u00e8re requ\u00eate va se lancer, et afficher le loader.<br \/>\nPuis la 2\u00e8me requ\u00eate va se lancer et afficher le loader.<br \/>\nmais entre temps, la 1\u00e8re requ\u00eate va se terminer et donc cacher le loader alors que la seconde requ\u00eate, elle, n&#8217;est pas finie.<\/p>\n<p>Ce n&#8217;est donc pas l&#8217;id\u00e9al.<\/p>\n<p>Donc pour faire \u00e7a bien on va ajouter un petit <span style=\"color: #000000;\"><strong>compteur<\/strong> <\/span>(que l&#8217;on va appeler &#8216;<span style=\"color: #2db3e7;\">nbRequetesEnCours<\/span>&#8216;), qui va ainsi nous permettre de cacher le loader uniquement quand toutes les requ\u00eates seront finies.<\/p>\n<p>Et ce qui donne le code suivant :<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\napp.factory('HttpInterceptor', ['$q', function($q) {\r\n        \/\/ Nombre de requ\u00eates en progression\r\n\tvar nbRequetesEnCours = 0;\r\n\treturn {\r\n                \/\/ M\u00e9thode appel\u00e9e avant une requ\u00eate http\r\n\t\trequest : function(config) {\r\n                        if (nbRequetesEnCours++ === 0) {\r\n\t                      loaderUtils.showLoader();\r\n                        }\r\n\t\t\treturn config || $q.when(config);\r\n\t\t},\r\n                \/\/ M\u00e9thode appel\u00e9e lors d'une r\u00e9ponse avec succ\u00e8s de la requ\u00eate\r\n\t\tresponse : function(response) {\r\n                        if (--nbRequetesEnCours === 0) {\r\n\t                      loaderUtils.hideLoader();\r\n                        }\r\n\t\t\treturn response || $q.when(response);\r\n\t\t},\r\n                \/\/ M\u00e9thode appel\u00e9e lors d'une r\u00e9ponse en erreur de la requ\u00eate\r\n\t\tresponseError : function(reason) {\r\n                        if (--nbRequetesEnCours === 0) {\r\n\t                      loaderUtils.hideLoader();\r\n                        }\r\n\t\t\treturn $q.reject(reason);\r\n\t\t},\r\n\t};\r\n}]);\r\n<\/pre>\n<p>Et maintenant il ne reste plus qu&#8217;\u00e0 enlever tous les appels \u00e0 &#8216;showLoader&#8217; et &#8216;hideLoader&#8217; de tous vos controlleurs, et le &#8216;httpInterceptor&#8217; s&#8217;occupera de tout et en toute transparente.<\/p>\n<p><strong>Vous avez maintenant un loader qui s&#8217;affiche \u00e0 chaque requ\u00eate ajax de fa\u00e7on autonome.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Le probl\u00e8me avec les requ\u00eates Ajax c&#8217;est qu&#8217;on ne sait jamais vraiment quand on va recevoir la r\u00e9ponse. En principe c&#8217;est rapide mais pour le si peu que la connexion est lente, que le serveur rame un peu \u00e0 ce moment l\u00e0, ou pire qu&#8217;il est en maintenance, le temps de r\u00e9ponse peut passer de &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/lalloue.fr\/blog\/afficher-un-loader-pendant-une-requete-ajax\/\" class=\"more-link\">Continuer la lecture <span class=\"screen-reader-text\"> \u00ab\u00a0Afficher un loader pendant une requ\u00eate Ajax\u00a0\u00bb<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[17,9],"tags":[19,39],"_links":{"self":[{"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/posts\/274"}],"collection":[{"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/comments?post=274"}],"version-history":[{"count":11,"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/posts\/274\/revisions"}],"predecessor-version":[{"id":318,"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/posts\/274\/revisions\/318"}],"wp:attachment":[{"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/media?parent=274"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/categories?post=274"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/lalloue.fr\/blog\/wp-json\/wp\/v2\/tags?post=274"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}