Serverabfrage mit AngularJS

86

Ich versuche AngularJS zu lernen. Mein erster Versuch, jede Sekunde neue Daten zu erhalten, hat funktioniert:

'use strict';

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

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

Wenn ich einen langsamen Server simuliere, indem ich den Thread 5 Sekunden lang schlafe, wartet er auf die Antwort, bevor er die Benutzeroberfläche aktualisiert und ein anderes Zeitlimit festlegt. Das Problem ist, wenn ich das Obige neu geschrieben habe, um Angular-Module und DI für die Modulerstellung zu verwenden:

'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);
    })();
};

Dies funktioniert nur, wenn die Serverantwort schnell ist. Wenn es eine Verzögerung gibt, spammt es 1 Anfrage pro Sekunde aus, ohne auf eine Antwort zu warten, und scheint die Benutzeroberfläche zu löschen. Ich denke, ich muss eine Rückruffunktion verwenden. Ich habe es versucht:

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

habe aber einen Fehler bekommen: "Fehler: destination.push ist keine Funktion" Dies basierte auf den Dokumenten für $ resource, aber ich habe die Beispiele dort nicht wirklich verstanden.

Wie mache ich den zweiten Ansatz?

Dave
quelle

Antworten:

115

Sie sollten die tickFunktion im Rückruf für aufrufen query.

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

    (function tick() {
        $scope.data = Data.query(function(){
            $timeout(tick, 1000);
        });
    })();
};
abhaga
quelle
3
Großartig, danke. Ich wusste nicht, dass Sie den Rückruf dort platzieren können. Damit wurde das Spam-Problem behoben. Ich habe auch die Datenzuweisung in den Rückruf verschoben, wodurch das Problem beim Löschen der Benutzeroberfläche gelöst wurde.
Dave
1
Freut mich helfen zu können! Wenn das Problem dadurch behoben wurde, können Sie diese Antwort akzeptieren, damit auch andere später davon profitieren können.
Abhaga
1
Angenommen, der obige Code gilt für Seite A und Controller A. Wie stoppe ich diesen Timer, wenn ich zu Seite B und Controller B navigiere?
Varun Verma
6
Der Vorgang zum Stoppen eines $ timeout wird hier unter docs.angularjs.org/api/ng.$timeout erläutert . Grundsätzlich gibt die $ timeout-Funktion ein Versprechen zurück, das Sie einer Variablen zuweisen müssen. Achten Sie dann darauf, wann dieser Controller zerstört wird: $ scope. $ On ('destroy', fn ());. Rufen Sie in der Rückruffunktion die Abbruchmethode von $ timeout auf und übergeben Sie das gespeicherte Versprechen: $ timeout.cancel (timeoutVar); Die $ Intervall-Dokumente haben tatsächlich ein besseres Beispiel ( docs.angularjs.org/api/ng.$interval )
Justin Lucas
1
@JustinLucas, nur für den Fall, dass es $ scope sein sollte. $ On ('$ destroy', fn ());
Tomate
33

Neuere Versionen von Angular haben $ interval eingeführt, das sogar noch besser als $ timeout für Serverabfragen funktioniert .

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;
    }
});
Bob
quelle
17
-1, ich denke nicht, dass $ interval geeignet ist, da Sie nicht auf die Serverantwort warten können, bevor Sie die nächste Anfrage senden. Dies kann zu vielen Anforderungen führen, wenn der Server eine hohe Latenz hat.
Treur
4
@ Treur: Während das heutzutage konventionelle Weisheit zu sein scheint, bin ich mir nicht sicher, ob ich damit einverstanden bin. In den meisten Fällen hätte ich lieber eine stabilere Lösung. Stellen Sie sich den Fall vor, in dem ein Benutzer vorübergehend offline geht, oder den Extremfall, in dem der Server nicht auf eine einzelne Anfrage reagiert. Die Benutzeroberfläche wird für Benutzer von $ timeout nicht mehr aktualisiert, da kein neues Timeout festgelegt wird. Für Benutzer von $ interval wird die Benutzeroberfläche dort fortgesetzt, wo sie aufgehört hat, sobald die Konnektivität wiederhergestellt ist. Natürlich ist es auch wichtig, vernünftige Verzögerungen zu wählen.
Bob
2
Ich denke, es ist bequemer, aber nicht belastbar. (Eine Toilette in meinem Schlafzimmer ist auch nachts sehr praktisch, aber irgendwann riecht es schlecht;)) Beim Abrufen der tatsächlichen Daten mit dem Intervall $ ignorieren Sie das Serverergebnis. Diesem fehlt eine Methode, um Ihren Benutzer zu informieren, die Datenintegrität zu erleichtern oder kurz gesagt: Verwalten Sie Ihren Anwendungsstatus im Allgemeinen. Sie können hierfür jedoch gängige $ http-Interceptors verwenden und das $ -Intervall abbrechen, wenn dies geschieht.
Treur
2
Wenn Sie $ q-Versprechen verwenden, können Sie einfach den Rückruf "finally" verwenden, um sicherzustellen, dass die Abfrage fortgesetzt wird, ob die Anforderung fehlschlägt oder nicht.
Tyson Nero
8
Eine bessere Alternative wäre, nicht nur das Erfolgsereignis, sondern auch das Fehlerereignis zu behandeln. Auf diese Weise können Sie die Anforderung erneut versuchen, wenn sie fehlschlägt. Sie könnten es sogar in einem anderen Intervall tun ...
Erdnuss
5

Hier ist meine Version mit rekursivem Polling. Dies bedeutet, dass auf die Serverantwort gewartet wird, bevor das nächste Timeout eingeleitet wird. Wenn ein Fehler auftritt, wird die Abfrage fortgesetzt, jedoch entspannter und entsprechend der Dauer des Fehlers.

Demo ist da

Hier mehr darüber geschrieben

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...';
   });
Guya
quelle
0

Wir können es einfach mit dem $ interval-Dienst abrufen. Hier ist ein detailliertes Dokument über $ interval
https://docs.angularjs.org/api/ng/service/$interval. Das
Problem bei der Verwendung von $ interval besteht darin, dass Sie $ http-Serviceaufrufe oder Serverinteraktionen ausführen und sich um mehr als $ Intervall verzögern Bevor Ihre eine Anfrage abgeschlossen ist, wird eine weitere Anfrage gestartet.
Lösung:
1. Das Abrufen sollte ein einfacher Status sein, der vom Server wie ein einzelnes Bit oder ein leichtes JSON abgerufen wird, und sollte daher nicht länger dauern als die definierte Intervallzeit. Sie sollten auch die Intervallzeit entsprechend definieren, um dieses Problem zu vermeiden.
2. Irgendwie passiert es aus irgendeinem Grund immer noch. Sie sollten ein globales Flag überprüfen, ob die vorherige Anforderung beendet wurde oder nicht, bevor Sie andere Anforderungen senden. Dieses Zeitintervall wird übersehen, aber die Anforderung wird nicht vorzeitig gesendet.
Auch wenn Sie einen Schwellenwert festlegen möchten, der nach einem bestimmten Wert ohnehin abgefragt werden soll, können Sie dies folgendermaßen tun.
Hier ist ein Arbeitsbeispiel. hier ausführlich erklärt

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;
    }
}]);
user1941574
quelle