web-dev-qa-db-fra.com

Appel AngularJS $ http dans un service, retourne les données résolues et non les promesses

Je souhaite savoir s’il est possible de passer un appel de service utilisant $http afin de renvoyer les données directement sans retourner de promesse. J'ai essayé d'utiliser le $q et de différer sans aucune chance.

Voici ce que je veux dire:

J'ai un service:

angular.module('myModule').factor('myService', ['$http', '$q',
    function($http, $q) {

        // Public API
        return {
            myServiceCall: function() {
                return $http.get('/server/call');
            }
        };
    }
]);

Et voici comment je l'appellerais:

// My controller:
myService.myServiceCall().then(function(data) {
  $scope.data = data;
});

Je veux éviter cela et souhaite plutôt avoir:

$scope.data = myService.myServiceCall();

Je veux qu'il soit complètement résolu à cette ligne, est-ce possible?

J'ai essayé quelques combinaisons de méthodes $ q, defer et 'then' mais je n'arrive pas à comprendre, car la méthode retourne immédiatement.

Modifier:

Si vous vous demandez pourquoi, la raison principale est que je voulais simplifier le code du contrôleur. Cela se fait facilement avec ngResource car ces appels sont automatiquement résolus dans des modèles. Je voulais donc éviter de devoir faire le tout. Alors. à chaque fois.

Ce n’est pas que je n’aime pas la nature asynchrone, la plupart de notre code l’utilise, c’est juste dans certains cas, avoir une manière synchrone est utile.

Je pense que pour l'instant, comme certains d'entre vous l'ont fait remarquer, j'utiliserai la ressource $ comme solution suffisamment proche.

12
iQ.

Je peux penser à cela de deux manières. Le premier est définitivement meilleur que le second.

La première façon: en utilisant la propriété resolve de routes

Vous pouvez utiliser la propriété resolve de l'objet de configuration d'itinéraire pour spécifier qu'une promesse doit être résolue et que la valeur résolue doit être utilisée pour la valeur de l'une des dépendances de votre contrôleur. Ainsi, dès que votre contrôleur s'exécute, la valeur est immédiatement disponible.

Par exemple, supposons que vous ayez le service suivant:

app.service('things', ['$http', '$q', function ($http, $q) {
    var deferred = $q.defer();

    $http({
        method: 'GET',
        url: '/things',
        cache: true
    }).success(function (data) {
        deferred.resolve(data);
    }).error(function (msg) {
        deferred.reject(msg);
    });

    return deferred.promise;
}]);

Ensuite, vous pouvez définir votre itinéraire comme ceci:

$routeProvider.when('/things', {
    controller: ['$scope', 'thingsData', function ($scope, thingsData) {
        $scope.allThings = thingsData;
    }],
    resolve: {
        thingsData: ['things', function (things) {
            return things;
        }]
    }
});

La clé de l'objet resolve correspond au nom de la deuxième dépendance du contrôleur. Ainsi, même si une promesse est retournée, cette promesse sera résolue avant l'exécution du contrôleur.

La deuxième façon: faites la demande bien avant que vous ayez besoin des données

La deuxième façon, qui n’est pas du tout idéale, et vous mordra probablement sur le dos, est de faire la demande de données à un stade précoce, par exemple lorsque votre application est initialisée, puis d’obtenir les données plus tard. , disons sur une page qui nécessite 3 clics pour accéder à la page d'accueil. Croisons les doigts, si les étoiles sont alignées, votre demande http sera retournée d'ici là et vous aurez les données à utiliser.

angular.module('myModule').factory('myService', ['$http', '$q', function($http, $q) {
    var data = [];

    function fetch() {
        $http.get('/server/call').then(function (d) { data = d; });
    }

    // Public API
    return {
        fetchData: fetch,
        myServiceCall: function () {
            return data;
        }
    };
}]);

Ensuite, dans la fonction d'exécution de votre module, faites la demande:

angular.module('myModule').run(['myService', function (myService) {
    myService.fetchData();
});

Ensuite, dans un contrôleur d'une page dont l'exécution est garantie au moins 3 secondes après le démarrage de l'application, vous pouvez procéder comme vous l'avez suggéré:

 angular.module('myModule').controller('deepPageCtrl', ['$scope', 'myService', function ($scope, myService) {
      $scope.data = myService.myServiceCall();
      if (angular.isArray($scope.data) && $scope.data.length === 0) {
           panic("data isn't loaded yet");
      }
  }]);

Bonus, tout aussi mauvais en troisième voie: demande synchrone AJAX

Faites comme suggéré par ricick, et utilisez la requête synchrone AJAX possible avec la méthode .ajax() de jQuery.

9
GregL

Ce n'est pas possible car les appels http sont intrinsèquement asynchrones. Autrement dit, vous devez attendre que le serveur réponde.

Le type de service $ resource peut résoudre ce problème en renvoyant un objet proxy qui est rempli avec les données renvoyées lors de la réponse du serveur, mais il n'est toujours pas totalement résolu sur une seule ligne.

Modifier:

En fait, il est possible de faire des requêtes HTTP synchrones avec la méthode jquery ajax, en définissant l'option "async" sur false.

Vous pouvez créer un service qui encapsule cela, mais votre application va se "verrouiller" tant que la demande attend une réponse du serveur. Dans la plupart des cas, cela serait donc une mauvaise pratique. Il existe également d'autres restrictions sur ce type de demande.

Voir la documentation pour plus de détails:

http://api.jquery.com/jQuery.ajax/

2
ricick

Il y a longtemps (dans les régions angulaires, en fait l'année dernière), c'était ainsi que cela fonctionnait.

Mais maintenant ce n'est plus le cas, je pense que cela est dû aux différences de comportement entre vue et contrôleur, plus ici à propos de la dépréciation: La promesse angularjs ne lie pas le template dans la version 1.2

0
Boris Charpentier