Interrogation du serveur avec AngularJS


86

J'essaye d'apprendre AngularJS. Ma première tentative d'obtenir de nouvelles données toutes les secondes a fonctionné:

'use strict';

function dataCtrl($scope, $http, $timeout) {
    $scope.data = [];

    (function tick() {
        $http.get('api/changingData').success(function (data) {
            $scope.data = data;
            $timeout(tick, 1000);
        });
    })();
};

Lorsque je simule un serveur lent en dormant le thread pendant 5 secondes, il attend la réponse avant de mettre à jour l'interface utilisateur et de définir un autre délai. Le problème est lorsque j'ai réécrit ce qui précède pour utiliser des modules angulaires et DI pour la création de modules:

'use strict';

angular.module('datacat', ['dataServices']);

angular.module('dataServices', ['ngResource']).
    factory('Data', function ($resource) {
        return $resource('api/changingData', {}, {
            query: { method: 'GET', params: {}, isArray: true }
        });
    });

function dataCtrl($scope, $timeout, Data) {
    $scope.data = [];

    (function tick() {
        $scope.data = Data.query();
        $timeout(tick, 1000);
    })();
};

Cela ne fonctionne que si la réponse du serveur est rapide. S'il y a un délai, il envoie 1 demande par seconde sans attendre de réponse et semble effacer l'interface utilisateur. Je pense que j'ai besoin d'utiliser une fonction de rappel. J'ai essayé:

var x = Data.get({}, function () { });

mais j'ai obtenu une erreur: "Erreur: destination.push n'est pas une fonction" Ceci était basé sur la documentation de $ resource mais je n'ai pas vraiment compris les exemples.

Comment faire fonctionner la deuxième approche?

Réponses:


115

Vous devriez appeler la tickfonction dans le rappel pour query.

function dataCtrl($scope, $timeout, Data) {
    $scope.data = [];

    (function tick() {
        $scope.data = Data.query(function(){
            $timeout(tick, 1000);
        });
    })();
};

3
Excellent, merci. Je ne savais pas que vous pouviez y mettre le rappel. Cela a résolu le problème du spam. J'ai également déplacé l'affectation des données à l'intérieur du rappel, ce qui a résolu le problème de suppression de l'interface utilisateur.
Dave

1
Heureux de pouvoir vous aider! Si cela a résolu le problème, vous pouvez accepter cette réponse afin que d'autres plus tard puissent également en bénéficier.
abhaga

1
En supposant que le code ci-dessus est pour pageA et controllerA. Comment arrêter ce minuteur lorsque je navigue vers la pageB et le contrôleurB?
Varun Verma

6
Le processus pour arrêter un $ timeout est expliqué ici docs.angularjs.org/api/ng.$timeout . Fondamentalement, la fonction $ timeout renvoie une promesse que vous devez attribuer à une variable. Ensuite, écoutez quand ce contrôleur est détruit: $ scope. $ On ('destroy', fn ()) ;. Dans la fonction de rappel, appelez la méthode cancel de $ timeout et transmettez la promesse que vous avez enregistrée: $ timeout.cancel (timeoutVar); Les documents $ intervalle ont en fait un meilleur exemple ( docs.angularjs.org/api/ng.$interval )
Justin Lucas

1
@JustinLucas, juste au cas où cela devrait être $ scope. $ On ('$ destroy', fn ());
Tomate

33

Des versions plus récentes d'angular ont introduit $ intervalle qui fonctionne encore mieux que $ timeout pour l'interrogation du serveur.

var refreshData = function() {
    // Assign to scope within callback to avoid data flickering on screen
    Data.query({ someField: $scope.fieldValue }, function(dataElements){
        $scope.data = dataElements;
    });
};

var promise = $interval(refreshData, 1000);

// Cancel interval on page changes
$scope.$on('$destroy', function(){
    if (angular.isDefined(promise)) {
        $interval.cancel(promise);
        promise = undefined;
    }
});

17
-1, je ne pense pas que $ intervalle convienne, car vous ne pouvez pas attendre la réponse du serveur avant d'envoyer la prochaine requête. Cela peut entraîner de nombreuses demandes lorsque le serveur a une latence élevée.
Treur

4
@Treur: Bien que cela semble être la sagesse conventionnelle de nos jours, je ne suis pas sûr d'être d'accord. Dans la plupart des cas, je préfère une solution plus résistante. Prenons le cas où un utilisateur se déconnecte temporairement ou le cas extrême de votre cas où le serveur ne répond pas à une seule demande. L'interface utilisateur arrêtera la mise à jour pour les utilisateurs de $ timeout car aucun nouveau délai d'expiration ne sera défini. Pour les utilisateurs de $ intervalle, l'interface utilisateur reprendra là où elle s'était arrêtée dès que la connectivité sera rétablie. De toute évidence, il est également important de choisir des délais raisonnables.
Bob

2
Je pense que c'est plus pratique, mais pas résilient. (Une toilette dans ma chambre est également très pratique la nuit, mais elle finira par sentir mauvais;)) Lorsque vous récupérez des données réelles en utilisant $ intervalle, vous ignorez le résultat du serveur. Il manque une méthode pour informer votre utilisateur, faciliter l'intégrité des données ou en bref: gérer l'état de votre application en général. Cependant, vous pouvez utiliser des intercepteurs $ http courants pour cela et annuler l'intervalle $ lorsque cela se produit.
Treur

2
Si vous utilisez $ q promesses, vous pouvez simplement utiliser le callback pour vous assurer que l'interrogation se poursuit, que la requête échoue ou non.
Tyson Nero

8
Une meilleure alternative serait de gérer non seulement l'événement de réussite, mais également l'événement d'erreur. De cette façon, vous pouvez réessayer la demande en cas d'échec. Vous pourriez même le faire à un intervalle différent ...
Peanut

5

Voici ma version utilisant le sondage récursif. Ce qui signifie qu'il attendra la réponse du serveur avant de lancer le prochain délai d'expiration. De plus, lorsqu'une erreur se produit, il continuera d'interroger mais dans un manoir plus détendu et en fonction de la durée de l'erreur.

La démo est là

En savoir plus ici

var app = angular.module('plunker', ['ngAnimate']);

app.controller('MainCtrl', function($scope, $http, $timeout) {

    var loadTime = 1000, //Load the data every second
        errorCount = 0, //Counter for the server errors
        loadPromise; //Pointer to the promise created by the Angular $timout service

    var getData = function() {
        $http.get('http://httpbin.org/delay/1?now=' + Date.now())

        .then(function(res) {
             $scope.data = res.data.args;

              errorCount = 0;
              nextLoad();
        })

        .catch(function(res) {
             $scope.data = 'Server error';
             nextLoad(++errorCount * 2 * loadTime);
        });
    };

     var cancelNextLoad = function() {
         $timeout.cancel(loadPromise);
     };

    var nextLoad = function(mill) {
        mill = mill || loadTime;

        //Always make sure the last timeout is cleared before starting a new one
        cancelNextLoad();
        $timeout(getData, mill);
    };


    //Start polling the data from the server
    getData();


        //Always clear the timeout when the view is destroyed, otherwise it will   keep polling
        $scope.$on('$destroy', function() {
            cancelNextLoad();
        });

        $scope.data = 'Loading...';
   });

0

Nous pouvons le faire en interrogeant facilement en utilisant le service $ interval. voici un document détaillé sur $ intervalle
https://docs.angularjs.org/api/ng/service/$interval Le
problème avec $ intervalle est que si vous faites un appel de service $ http ou une interaction avec le serveur et si retardé de plus de $ intervalle de temps puis avant que votre demande ne se termine, il commence une autre demande.
Solution:
1. L'interrogation doit être un simple état provenant du serveur comme un simple bit ou un json léger et ne devrait donc pas prendre plus de temps que votre intervalle de temps défini. Vous devez également définir l'heure de l'intervalle de manière appropriée pour éviter ce problème.
2. D'une manière ou d'une autre, cela se produit toujours pour une raison quelconque, vous devez vérifier un indicateur global indiquant que la requête précédente s'est terminée ou non avant d'envoyer toute autre requête. Il manquera cet intervalle de temps, mais il n'enverra pas de demande prématurément.
De plus, si vous souhaitez définir une valeur de seuil qui, après une certaine valeur, l'interrogation doit être définie, vous pouvez le faire de la manière suivante.
Voici un exemple de travail. expliqué en détail ici

angular.module('myApp.view2', ['ngRoute'])
.controller('View2Ctrl', ['$scope', '$timeout', '$interval', '$http', function ($scope, $timeout, $interval, $http) {
    $scope.title = "Test Title";

    $scope.data = [];

    var hasvaluereturnd = true; // Flag to check 
    var thresholdvalue = 20; // interval threshold value

    function poll(interval, callback) {
        return $interval(function () {
            if (hasvaluereturnd) {  //check flag before start new call
                callback(hasvaluereturnd);
            }
            thresholdvalue = thresholdvalue - 1;  //Decrease threshold value 
            if (thresholdvalue == 0) {
                $scope.stopPoll(); // Stop $interval if it reaches to threshold
            }
        }, interval)
    }

    var pollpromise = poll(1000, function () {
        hasvaluereturnd = false;
        //$timeout(function () {  // You can test scenario where server takes more time then interval
        $http.get('http://httpbin.org/get?timeoutKey=timeoutValue').then(
            function (data) {
                hasvaluereturnd = true;  // set Flag to true to start new call
                $scope.data = data;

            },
            function (e) {
                hasvaluereturnd = true; // set Flag to true to start new call
                //You can set false also as per your requirement in case of error
            }
        );
        //}, 2000); 
    });

    // stop interval.
    $scope.stopPoll = function () {
        $interval.cancel(pollpromise);
        thresholdvalue = 0;     //reset all flags. 
        hasvaluereturnd = true;
    }
}]);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.