Afficher un loader pendant une requête Ajax

AngularJS-large

Le problème avec les requêtes Ajax c’est qu’on ne sait jamais vraiment quand on va recevoir la réponse.
En principe c’est rapide mais pour le si peu que la connexion est lente, que le serveur rame un peu à ce moment là, ou pire qu’il est en maintenance, le temps de réponse peut passer de 1 secondes à un temps indéfini.

Et pendant ce temps l’utilisateur ne sait pas ce qui se passe : d’où l’intérêt d’intégrer un loader (ou mire de chargement) à votre page pendant l’exécution de votre requête.
Voici un petit exemple pour le mettre en place :

 

Introduction :

Tout commence par l’envoi d’une requête. Cela se fait via la méthode $http d’AngularJs comme l’exemple ci-dessous :

app.controller('MonController', ['$scope', '$http',
               function($scope, $http) {

	var url = 'MON_URL';

	$http.get(url).then(function success(response) {
		// Return result
		return response.data.result;
	}, function error(reason) {
		return false;
	});

]);

Si la réponse est correcte on passe donc dans la méthode “success”, sinon, dans la méthode “error”.

On veut donc maintenant lancer le loader avant que la requête soit envoyée, et cacher le loader une fois la réponse récupérée.

 

Déjà, comment on affiche le loader ?

Pour ça je vous conseille la petite librairie ‘spinner’ qui fonctionne très bien (spinner.js).
Il existe d’autres solutions mais celle-ci à l’avantage d’être légère et de n’avoir pas nécessairement besoin d’inclure JQuery pour l’utiliser.

Pour l’utiliser vous avez une documentation au lien que je vous ai donné ci-dessus, mais vu que je suis d’humeur sympathique je vais vous faire un petit résumé en français ici :

Installation :

Il vous suffit de récupérer le fichier “spin.js” et de l’ajouter à votre projet.
Puis ensuite il sera nécessaire de l’inclure dans votre page html tel que ci-dessous :

<!doctype html>
<html>
<head>
<script type="text/javascript" src="spin.js"></script>

</head>
...

Si vous utilisez bower, une petite ligne de commande et le tour sera joué :

$ bower install spin.js --save
Utilisation :

Sur ma balise ‘body’ de index.html j’apelle une méthode ‘onLoad()’

<!doctype html>
<html>
	<head>
	</head>
	<body onload="onLoad()">
		<!-- CONTENT -->
		<div ng-view></div>

		<!-- LOADING SPINNER -->
		<div id="loading-ws-bar"></div>
		<div id="loading-ws-background-page"></div>
	</body>
...

que je définie dans mon ‘main.js‘ :

function onLoad() {
	loaderUtils.generateLoading('loading-ws-bar', ['#157199','#1B91C4']);
}

Et il ne vous manque plus que la fameuse classe ‘loaderUtils‘ pour finir d’intégrer votre magnifique spinner.
Je vous donne la classe entière parce que vous verrez qu’on va réutiliser les méthodes dans la suite :

'use strict';

function LoaderUtils() {
}

/**
 * Generate the spin loader
 */
LoaderUtils.prototype.generateLoading = function(domElementId, spinnerColor) {

	var opts = {
		lines : 12, // The number of lines to draw
		length : 15, // The length of each line
		width : 15, // The line thickness
		radius : 30, // The radius of the inner circle
		corners : 1, // Corner roundness (0..1)
		rotate : 0, // The rotation offset
		direction : 1, // 1: clockwise, -1: counterclockwise
		color : spinnerColor, // #rgb or #rrggbb or array of colors
		speed : 1, // Rounds per second
		trail : 90, // Afterglow percentage
		shadow : true, // Whether to render a shadow
		hwaccel : true, // Whether to use hardware acceleration
		className : 'spinner', // The CSS class to assign to the spinner
		zIndex : 2e9, // The z-index (defaults to 2000000000)
		top : '50%', // Top position relative to parent in px
		left : '50%' // Left position relative to parent in px
	};

	new Spinner(opts).spin(document.getElementById(domElementId));
};

/**
 * show the loader
 */
LoaderUtils.prototype.showLoader = function() {
	$('#loading-ws-bar, #loading-ws-background-page').show();
};

/**
 * hide the loader
 */
LoaderUtils.prototype.hideLoader = function() {
	$('#loading-ws-bar, #loading-ws-background-page').hide();
};

var loaderUtils = new LoaderUtils();

Bien-sur toutes les propriétés du spinner sont personnalisables. Pour plus d’infos regardez les commentaires en bout de ligne de l’objet ‘opts‘.

Et pour finir, voici un petit css à intégrer pour un meilleur affichage, mais là pareil, tout le monde peut personnaliser comme il l’entend :

/* 	Spinner css stylesheet
	See 'loaderUtils.js' to know how to use it
*/

/* Transparent background color to disable action on page */
#loading-ws-background-page {
    background-color: rgba(0, 0, 0, 0.5);
    bottom: 0;
    display:none;
    left: 0;
    position: fixed;
    right: 0;
    top: 0;
    z-index: 10000;
}
#loading-ws-bar {
    border-radius: 2px;
	display:none;
    height: 100px;
    left: 50%;
    margin-left: -75px;
    margin-top: -50px;
    position: fixed;
    top: 50%;
    width: 150px;
    z-index: 10001;
}

Mais comme vous avez mis par défaut un ‘display:none‘ dans votre css le spinner ne s’affichera jamais. C’est normal !
On va l’afficher seulement quand on enverra une requête, donc on va le faire côté javascript.

 

Afficher le loader à chaque requête Ajax :

Maintenant qu’on a intégré ‘spin.js‘ et qu’on l’a paramétré, rien de plus simple pour l’afficher vous allez voir.
On reprend donc notre exemple d’appel Ajax du début et on y ajoute 3 petits bouts de code :

app.controller('MonController', ['$scope', '$http',
               function($scope, $http) {

	var url = 'MON_URL';

	// Display loader
	loaderUtils.showLoader();

	$http.get(url).then(function success(response) {
		// Display loader
		loaderUtils.hideLoader();
		// Return result
		return response.data.result;
	}, function error(reason) {
		// Display loader
		loaderUtils.hideLoader();
		return false;
	});

]);

Vous voyez donc qu’on à ajouté un appel à la classe ‘loaderUtils‘ pour afficher et cacher le spinner en fonction de l’état de la requête.
Et c’est tout !
Il vous suffira maintenant d’appeler les méthodes ‘showLoader‘ et ‘hideLoader‘ à chaque requête Ajax pour afficher votre loader.

 

Utilisation un peu plus poussée :

Mais j’entend déjà quelques uns dire : “C’est sympas ton truc, mais faut le faire à chaque requête du coup !”.

Et bien OUI, la méthode expliquée ci-dessus oblige à intégrer les méthodes d’affichage et pour cacher le loader avant et après CHAQUE requête. Et c’est pénible.
Alors est-ce qu’il existe une manière de le faire de façon transparente à chaque requête systématiquement?

OUI !

Cette technique est un peu plus poussée, mais je vais vous expliquer comment faire :

Pour cela on va intercepter les requêtes avec un ‘interceptor’.
Un interceptor est une factory que l’on va attacher au $httpProvider. De cette manière on va pouvoir intercepter les requêtes faites via le service $http.
Si vous voulez un peu plus de documentation vous pouvez lire la doc officielle du service $http ici.

Mise en place :

On va d’abord indiquer au ‘$httpProvider‘ qu’on lui assigne un intercepteur. Pour cela j’ajoute les lignes suivantes dans mon ‘app.js‘ :

app.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('httpInterceptor');
}]);

Ainsi toutes les requêtes http faites par le service ‘$http‘ seront interceptées par notre service ‘httpInterceptor‘.
Et ensuite on va créer le fameux httpInterceptor.

app.factory('HttpInterceptor', ['$q', function($q) {
	return {
                // Méthode appelée avant une requête http
		request : function(config) {
			return config || $q.when(config);
		},
                // Méthode appelée lors d'une réponse avec succès de la requête
		response : function(response) {
			return response || $q.when(response);
		},
                // Méthode appelée lors d'une réponse en erreur de la requête
		responseError : function(reason) {
			return $q.reject(reason);
		},
	};
}]);
Et le loader dans tout ça ?

Qu’est-ce qu’on fait exactement ici?

On reprend les méthodes du ‘$httpProvider‘ appelées et on les intercepte pour les réécrire. On a donc ici 3 méthodes :

  • request : méthode appelée avant que la requête Ajax soit exécutée.
  • response : méthode appelée lorsqu’on reçoit la réponse de la requête quand elle s’est bien déroulée.
  • responseError : méthode appelée lorsqu’on reçoit une réponse en erreur de la requête envoyée.

Tel que décrit ci-dessus, je retourne les mêmes informations que celles réceptionnées, donc l’intercepteur ne sert à rien dans le cas présent.
Mais si vous voyez à peu près ou je veux aller, on va pouvoir utiliser ces méthodes pour appeler notre fameux loader !

Ainsi, ce sera le httpInterceptor qui se chargera d’afficher ou de cacher le spinner et tout ça de façon transparente pour les controlleurs.
Il suffit donc d’appeler le ‘showLoader’ depuis la méthode ‘request’ et le ‘hideLoader’ depuis les méthodes ‘response’ et ‘responseError’.

Et c’est tout?

Et bien presque, mais avant de vous donner le code définitif, j’attire votre attention sur un petit point important :

Si vous lancez plusieurs requêtes à la suite que va-t-il se passer?

Et bien si on fait  comme je l’ai dis juste avant, la 1ère requête va se lancer, et afficher le loader.
Puis la 2ème requête va se lancer et afficher le loader.
mais entre temps, la 1ère requête va se terminer et donc cacher le loader alors que la seconde requête, elle, n’est pas finie.

Ce n’est donc pas l’idéal.

Donc pour faire ça bien on va ajouter un petit compteur (que l’on va appeler ‘nbRequetesEnCours‘), qui va ainsi nous permettre de cacher le loader uniquement quand toutes les requêtes seront finies.

Et ce qui donne le code suivant :

app.factory('HttpInterceptor', ['$q', function($q) {
        // Nombre de requêtes en progression
	var nbRequetesEnCours = 0;
	return {
                // Méthode appelée avant une requête http
		request : function(config) {
                        if (nbRequetesEnCours++ === 0) {
	                      loaderUtils.showLoader();
                        }
			return config || $q.when(config);
		},
                // Méthode appelée lors d'une réponse avec succès de la requête
		response : function(response) {
                        if (--nbRequetesEnCours === 0) {
	                      loaderUtils.hideLoader();
                        }
			return response || $q.when(response);
		},
                // Méthode appelée lors d'une réponse en erreur de la requête
		responseError : function(reason) {
                        if (--nbRequetesEnCours === 0) {
	                      loaderUtils.hideLoader();
                        }
			return $q.reject(reason);
		},
	};
}]);

Et maintenant il ne reste plus qu’à enlever tous les appels à ‘showLoader’ et ‘hideLoader’ de tous vos controlleurs, et le ‘httpInterceptor’ s’occupera de tout et en toute transparente.

Vous avez maintenant un loader qui s’affiche à chaque requête ajax de façon autonome.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Prouve moi que tu es bien humain ->