Odpytywanie serwera za pomocą AngularJS

86

Próbuję się nauczyć AngularJS. Moja pierwsza próba uzyskania nowych danych co sekundę zadziałała:

'use strict';

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

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

Kiedy symuluję powolny serwer, śpiąc wątek przez 5 sekund, czeka na odpowiedź przed aktualizacją interfejsu użytkownika i ustawieniem kolejnego limitu czasu. Problem pojawia się, gdy przepisałem powyższe, aby użyć modułów Angular i DI do tworzenia modułów:

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

Działa to tylko wtedy, gdy odpowiedź serwera jest szybka. Jeśli występuje jakieś opóźnienie, wysyła 1 żądanie na sekundę bez czekania na odpowiedź i wydaje się czyścić interfejs użytkownika. Myślę, że muszę użyć funkcji oddzwaniania. Próbowałem:

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

ale pojawił się błąd: „Błąd: miejsce docelowe.push nie jest funkcją” Zostało to oparte na dokumentacji dla $ zasób, ale nie rozumiałem tam przykładów.

Jak sprawić, by drugie podejście zadziałało?

Dave
źródło

Odpowiedzi:

115

Powinieneś wywołać tickfunkcję w wywołaniu zwrotnym dla query.

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

    (function tick() {
        $scope.data = Data.query(function(){
            $timeout(tick, 1000);
        });
    })();
};
abhaga
źródło
3
Wspaniale dzięki. Nie wiedziałem, że możesz tam umieścić oddzwonienie. To rozwiązało problem spamowania. Przeniosłem również przypisanie danych do wnętrza wywołania zwrotnego, co rozwiązało problem z czyszczeniem interfejsu użytkownika.
Dave
1
Cieszę się, że mogę pomóc! Jeśli to rozwiązało problem, możesz zaakceptować tę odpowiedź, aby inni później również mogli z niej skorzystać.
abhaga
1
Zakładając, że powyższy kod dotyczy pageA i controllerA. Jak zatrzymać ten licznik czasu, gdy przejdę do strony B i kontrolera B?
Varun Verma
6
Proces zatrzymywania $ timeout jest wyjaśniony tutaj docs.angularjs.org/api/ng.$timeout . Zasadniczo funkcja $ timeout zwraca obietnicę, którą musisz przypisać do zmiennej. Następnie nasłuchuj, kiedy ten kontroler zostanie zniszczony: $ scope. $ On ('zniszcz', fn ()) ;. W funkcji zwrotnej wywołaj metodę anulowania $ timeout i przekaż zapisaną obietnicę: $ timeout.cancel (timeoutVar); Dokumenty z interwałami $ mają faktycznie lepszy przykład ( docs.angularjs.org/api/ng.$interval )
Justin Lucas
1
@JustinLucas, na wszelki wypadek powinien to być $ scope. $ On ('$ zniszcz', fn ());
Pomidor
33

Nowsze wersje angulara wprowadziły interwał $ interwał, który działa nawet lepiej niż $ timeout przy odpytywaniu serwera.

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;
    }
});
Pion
źródło
17
-1, nie sądzę, aby interwał $ interwał był odpowiedni, ponieważ nie możesz czekać na odpowiedź serwera przed wysłaniem następnego żądania. Może to powodować wiele żądań, gdy serwer ma duże opóźnienia.
Treur
4
@Treur: Chociaż w dzisiejszych czasach wydaje się to być konwencjonalną mądrością, nie jestem pewien, czy się zgadzam. W większości przypadków wolałbym mieć bardziej odporne rozwiązanie. Weź pod uwagę przypadek, w którym użytkownik tymczasowo przechodzi w tryb offline lub skrajny przypadek, w którym serwer nie odpowiada na żadne żądanie. Interfejs użytkownika przestanie aktualizować się dla użytkowników $ timeout, ponieważ nowy limit czasu nie zostanie ustawiony. W przypadku użytkowników interwału $ interwał interfejs użytkownika zostanie uruchomiony w miejscu, w którym został przerwany, zaraz po przywróceniu łączności. Oczywiście ważne jest również wybieranie rozsądnych opóźnień.
Bob
2
Myślę, że jest to wygodniejsze, ale nie sprężyste. (Toaleta w mojej sypialni jest również bardzo wygodna w nocy, ale w końcu zacznie brzydko pachnieć;)) Podczas pobierania rzeczywistych danych z użyciem interwału $ interwał ignorujesz wynik serwera. Brakuje metody informowania użytkownika, ułatwiania integralności danych lub w skrócie: zarządzania stanem aplikacji w ogóle. Możesz jednak użyć do tego typowych przechwytywaczy $ http i anulować interwał $, gdy tak się stanie.
Treur
2
Jeśli używasz obietnic $ q, możesz po prostu użyć wywołania zwrotnego w końcu, aby upewnić się, że sondowanie będzie kontynuowane, niezależnie od tego, czy żądanie się nie powiedzie, czy nie.
Tyson Nero
8
Lepszą alternatywą byłoby obsłużenie nie tylko zdarzenia sukcesu, ale także zdarzenia błędu. W ten sposób możesz ponowić żądanie, jeśli się nie powiedzie. Możesz to nawet zrobić w innym odstępie czasu ...
Peanut
5

Oto moja wersja z odpytywaniem rekurencyjnym. Oznacza to, że będzie czekał na odpowiedź serwera przed zainicjowaniem następnego limitu czasu. Ponadto, gdy wystąpi błąd, odpytywanie będzie kontynuowane, ale w bardziej swobodnej rezydencji i zgodnie z czasem trwania błędu.

Demo jest tutaj

Więcej o tym napisałem tutaj

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
źródło
0

Możemy to łatwo zrobić odpytywaniem za pomocą usługi $ interwał. tutaj jest szczegółowy dokument na temat $ interwału
https://docs.angularjs.org/api/ng/service/$interval
Problem z używaniem $ interwału polega na tym, że jeśli wykonujesz wywołanie usługi $ http lub interakcję z serwerem i jeśli opóźnienie przekracza $ interwał następnie zanim jedno żądanie zostanie zakończone, rozpocznie kolejne żądanie.
Rozwiązanie:
1. Sondowanie powinno być prostym pobieraniem statusu z serwera, takim jak pojedynczy bit lub lekki plik JSON, więc nie powinno trwać dłużej niż zdefiniowany interwał. Aby uniknąć tego problemu, należy również odpowiednio zdefiniować przedział czasu.
2. Jakoś to się nadal dzieje z jakiegokolwiek powodu, powinieneś sprawdzić globalną flagę, że poprzednie żądanie zakończyło się, czy nie, przed wysłaniem jakichkolwiek innych żądań. Pominie ten przedział czasu, ale nie wyśle ​​żądania przedwcześnie.
Również jeśli chcesz ustawić wartość progową, która po jakiejś wartości powinna być ustawiona tak czy inaczej, możesz to zrobić w następujący sposób.
Oto działający przykład. szczegółowo wyjaśniono tutaj

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
źródło