Utiliser les promises avec ngResource

Utiliser les promises avec ngResource

Nous avions déjà parlé des ressources d’AngularJS, notamment comment les utiliser simplement avec des callbacks.

Après avoir assisté au CaenJS (évènement JavaScript très très sympa) en particulier une conférence sur le sujet, j’avais envie d’essayer les promises avec ngResource.

C’est chose faite. Et c’est pas mal du tout.

Passer des callbacks aux promises…

Dans la conférence en question, JavaScript et ses promesses, Sylvain Zyssman (son blog) nous montrait pourquoi et comment remplacer les callbacks par des promises.

Je vous conseille de visionner la présentation, mais pour résumer, les promises permettent de simplifier l’implémentation du code, de garantir son exécution, et de gérer les cas d’erreurs de manière claire.

…Avec AngularJS

Le but ici est de voir comment s’articulent les promesses avec $resource en AngularJS.

Voilà un exemple d’utilisation avec des callbacks :

angular.module('promiseTestApp')
.controller('LoginWithCallbacksCtrl', function ($scope, Connection) {
	$scope.connect = function(login, password) {
		$scope.showLoader();
		Connection.get(
			{'login': login, 'password': password},
			function() { // success
				$scope.notification('Connection successful');
				$scope.hideLoader();
			},
			function(response) { // failure
				$scope.notificationError('Connection failed: ' + response.data.reason); 
				$scope.hideLoader();
			}
		);
	};

});

La même chose avec des promises :

angular.module('promiseTestApp')
.controller('LoginWithPromisesCtrl', function ($scope, Connection) {
	$scope.connect = function(login, password) {
		$scope.showLoader();
		Connection.get({'login': login, 'password': password}).$promise
		.then(function() { // success
			$scope.notification('Connection successful');
		})
		.catch(function(response) { // failure
			$scope.notificationError('Connection failed: ' + response.data.reason);
		})
		['finally'](function() {
			$scope.hideLoader();
		});
	};

});

Le code est simple, alors l’avantage peut sembler minime au niveau de la présentation, mais on peut déjà repérer facilement ce qui est fait en cas de succès avec then(), et ce qui est fait en cas d’erreur, avec catch().

Aussi, finally() qui accueille le code qui sera fait dans tous les cas est également très pratique.

Une note à propos de finally() : comme il s’agit d’un mot réservé d’IE8, vous ne devriez pas l’écrire comme un méthode, mais comme une entrée de tableau (sauf si vous ne supportez pas IE8).

Récupérer le résultat

Avec ngResource et les callbacks, nous étions habitués à écrire ceci :

$scope.users = User.query(paramètres, callback en cas de succès, callback en cas d'échec);

Avec les promesses, il y a un piège (ou pas) :

$scope.users = User.query(paramètres).$promise
.then(callback en cas de succès)
.catch(callback en cas d'échec);

Dans ce cas, $scope.users reste désespérément vide, car il contient le résultat de l’affectation.

Pour résoudre ce problème, et bien affecter la ressource, vous avez le choix entre deux solutions.

La première, affecter le résultat dans then() :

User.query(paramètres).$promise
.then(function(result) { $scope.users = result; })
.catch(callback en cas d'échec);

A mon avis, cette solution est contraignante, d’abord parce qu’il faut y penser, et aussi parce qu’elle rend l’utilisation de then() obligatoire, alors que ce n’est pas toujours nécessaire.

L’autre solution, procéder en deux étapes :

$scope.users = User.query(paramètres);
$scope.users.$promise
.then(callback en cas de succès)
.catch(callback en cas d'échec);

Ici, on affecte la ressource comme avec les callbacks, puis on définit la promesse sur celle-ci.

C’est un peu contraignant, mais, à mon avis, moins que la première solution…

Chaînage

L’un des principaux avantages des promises est la possibilité d’enchainer plusieurs actions, chacune dans un then() et de bénéficier du catch() et du finally().

angular.module('promiseTestApp')
.controller('LoginWithChainedPromisesCtrl', function ($scope, Connection, UserData) {
	$scope.connect = function(login, password) {
		$scope.showLoader();
		Connection.get({'login': login, 'password': password}).$promise
		.then(function(result) { // connection success
			$scope.notification('Connection successful');
			$scope.userData = UserData.get({'token': result.token});
			return $scope.userData.$promise;
		})
		.then(function(result) { // user data success
			$scope.notification('Welcome ' + result.nickName);
		})
		.catch(function(response) { // failure
			$scope.notificationError('Connection failed: ' + response.data.reason);
		})
		['finally'](function() {
			$scope.hideLoader();
		});
	};

});

Dans cet exemple, le premier then() fait appel au service Connection, récupère les informations avec UserData et retourne la promesse de la ressource.

C’est d’ailleurs ce retour qui permet de faire fonctionner le chaînage. Une bonne pratique pourrait être de retourner une promise dans chaque then() ?

La méthode catch() permet de gérer toutes les erreurs au même endroit.

Le mot de la fin

Les promises, c’est bon, mangez-en ?

L'illustration de cet article est une image sous licence CC BY-SA 4.0 par LucieStg

Cet article vous a été utile ? Partage it !

3 réflexions au sujet de « Utiliser les promises avec ngResource »

  1. Salut, super tuto mais j’aurai une question..

    Je débute avec angular et je n’arrive pas à faire du chainâge de méthode sur la requête .$save() ..

    J’ai quelque chose de très basique :

    services/User.js


    app.factory('User', ['$resource',
    function($resource){
    return $resource('./app_dev.php/api/users/:userId', {userId:'@Id'});
    }]
    );

    controllers/SignUpCtrl.js


    app.controller('SignupCtrl', ['$scope', '$resource', 'User', function($scope, $resource, User){

    $scope.user = new User();

    $scope.update = function(user){
    $scope.user.$save().$promise
    .then(function(result){
    console.log('it worked!' + result);
    })
    .catch(function(response){
    console.log('it failed!' + response);
    });

    };

    }]);

    J’ai une erreur me disant qu’il est impossible d’accéder a la méthode then de undefined, j’en déduis qu’il n’a pas accès à $promise depuis la méthode $save() …

    Je comprends pas très bien pourquoi, une idée ?

    1. Bonjour Kaz,

      Les instances d’actions ($save et toutes les méthodes de ressource qui commencent par $) retournent directement une promesse.

      En clair, pas besoin de $promise, $scope.user.$save().then() directement devrait faire l’affaire.

      David

      1. D’acc, super je vais tester ça 🙂 !

        Merci beaucoup de ta réponse rapide, et bravo pour ton très bon blog, rempli d’articles de qualité 🙂 !

        Bonne journée

        Kaz

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Si vous le souhaitez, renseignez le champ 'Nom' de cette façon : 'VotreNom@VotreMotClef' pour obtenir une ancre optimisée pour les moteurs de recherche.