Estou tentando aprender AngularJS. Minha primeira tentativa de obter novos dados a cada segundo funcionou:
'use strict';
function dataCtrl($scope, $http, $timeout) {
$scope.data = [];
(function tick() {
$http.get('api/changingData').success(function (data) {
$scope.data = data;
$timeout(tick, 1000);
});
})();
};
Quando eu simulo um servidor lento dormindo o thread por 5 segundos, ele aguarda a resposta antes de atualizar a IU e definir outro tempo limite. O problema é quando eu reescrevi o acima para usar módulos Angular e DI para a criação do módulo:
'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);
})();
};
Isso só funciona se a resposta do servidor for rápida. Se houver algum atraso, ele envia 1 solicitação por segundo sem esperar por uma resposta e parece limpar a IU. Acho que preciso usar uma função de retorno de chamada. Eu tentei:
var x = Data.get({}, function () { });
mas ocorreu um erro: "Erro: o destino.push não é uma função" Isso foi baseado na documentação de $ resource, mas eu realmente não entendi os exemplos lá.
Como faço a segunda abordagem funcionar?
fonte
Versões mais recentes do angular introduziram $ interval que funciona ainda melhor do que $ timeout para polling do servidor.
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; } });
fonte
Aqui está minha versão usando pesquisa recursiva. O que significa que ele aguardará a resposta do servidor antes de iniciar o próximo tempo limite. Além disso, quando ocorrer um erro, ele continuará a votação, mas de uma forma mais tranquila e de acordo com a duração do erro.
A demonstração está aqui
Escreveu mais sobre isso aqui
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...'; });
fonte
Podemos fazer isso facilmente usando o serviço $ interval. aqui está o documento de detalhes sobre $ interval
https://docs.angularjs.org/api/ng/service/$interval O
problema de usar $ interval é que se você estiver fazendo $ http chamada de serviço ou interação com o servidor e se atrasar mais de $ interval time então, antes que sua solicitação seja concluída, ela inicia outra.
Solução:
1. O polling deve ser um status simples obtido do servidor, como um bit único ou json leve, portanto, não deve demorar mais do que o tempo de intervalo definido. Você também deve definir o tempo de intervalo de forma adequada para evitar esse problema.
2. De alguma forma isso ainda está acontecendo por qualquer motivo, você deve verificar um sinalizador global de que a solicitação anterior foi concluída ou não antes de enviar qualquer outra solicitação. Ele perderá esse intervalo de tempo, mas não enviará a solicitação prematuramente.
Além disso, se você quiser definir o valor limite que, após algum valor, a pesquisa deve ser definida, você pode fazer da seguinte maneira.
Aqui está um exemplo de trabalho. explicado em detalhes aqui
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; } }]);
fonte