Опитування сервера за допомогою AngularJS


86

Я намагаюся вивчити AngularJS. Моя перша спроба отримати нові дані щосекунди спрацювала:

'use strict';

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

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

Коли я моделюю повільний сервер, сплячи потік протягом 5 секунд, він чекає відповіді перед оновленням інтерфейсу та встановленням іншого тайм-ауту. Проблема полягає в тому, що я переписав вищезазначене для використання модулів Angular та DI для створення модулів:

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

Це працює, лише якщо реакція сервера швидка. Якщо є затримка, він видає 1 запит на секунду, не чекаючи відповіді, і, здається, очищає інтерфейс. Думаю, мені потрібно використовувати функцію зворотного виклику. Я намагався:

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

але отримав помилку: "Помилка: destination.push не є функцією" Це базувалося на документах для ресурсу $, але я не дуже розумів приклади там.

Як змусити другий підхід спрацювати?

Відповіді:


115

Ви повинні викликати tickфункцію у зворотному дзвінку для query.

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

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

3
Чудово, дякую. Я не знав, що ти можеш там передзвонити. Це вирішило проблему спаму. Я також перемістив призначення даних всередину зворотного виклику, який вирішив проблему очищення інтерфейсу.
Дейв

1
Радий, що можу допомогти! Якщо це вирішило проблему, ви можете прийняти цю відповідь, щоб інші пізніше також могли скористатися нею.
abhaga

1
Якщо припустити, що наведений вище код призначений для сторінки А та контролера А. Як зупинити цей таймер, коли я переходжу на сторінку B та контролерB?
Варун Верма

6
Процес зупинки $ timeout описаний тут docs.angularjs.org/api/ng.$timeout . В основному, функція $ timeout повертає обіцянку, яку потрібно призначити змінній. Потім прослухайте, коли цей контролер буде знищений: $ scope. $ On ('знищити', fn ()) ;. У функції зворотного виклику зателефонуйте методу скасування $ timeout та передайте збережену обіцянку: $ timeout.cancel (timeoutVar); Документи $ interval насправді мають кращий приклад ( docs.angularjs.org/api/ng.$interval )
Джастін Лукас

1
@JustinLucas, про всяк випадок це має бути $ scope. $ On ('$ знищити', fn ());
Помідор

33

Останні версії angular ввели $ interval, який працює навіть краще, ніж $ timeout для опитування на сервері.

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, я вважаю, що $ interval не підходить, оскільки ви не можете дочекатися відповіді сервера перед відправкою наступного запиту. Це може спричинити багато запитів, коли сервер має високу затримку.
Треур,

4
@Treur: Хоча сьогодні це здається загальноприйнятою мудрістю, я не впевнений, що згоден. У більшості випадків я б віддав перевагу більш стійкому рішенню. Розглянемо випадок, коли користувач тимчасово переходить у офлайн-режим, або крайній випадок, коли сервер не відповідає на жоден запит. Інтерфейс користувача припинить оновлення для користувачів $ timeout, оскільки новий тайм-аут не буде встановлений. Для користувачів інтервалу $ користувальницький інтерфейс продовжить роботу там, де він зупинився, як тільки з’єднання відновиться. Очевидно, що важливим є також вибір розумних затримок.
Боб

2
Я думаю, що це зручніше, але не еластично. (Туалет у моїй спальні також дуже зручний вночі, але з часом він починає неприємно пахнути;)) При отриманні фактичних даних за допомогою $ interval ви ігноруєте результат серверів. Тут бракує методу інформування користувача, полегшення цілісності даних або, коротше кажучи: управління станом вашої програми загалом. Однак ви можете використовувати для цього загальні перехоплювачі $ http і скасувати інтервал $, коли це станеться.
Треур,

2
Якщо ви використовуєте обіцянки $ q, ви можете просто використати нарешті зворотний виклик, щоб переконатися, що опитування триває незалежно від того, чи запит не вдається.
Тайсон Неро,

8
Кращою альтернативою було б обробляти не лише подію успіху, але й подію помилки. Таким чином, ви можете спробувати запит ще раз, якщо він не вдається. Ви навіть можете робити це з різним інтервалом ...
Арахіс

5

Ось моя версія за допомогою рекурсивного опитування. Це означає, що він буде чекати відповіді сервера, перш ніж ініціювати наступний тайм-аут. Крім того, при появі помилки опитування продовжуватиметься, але в більш спокійній садибі та відповідно до тривалості помилки.

Демо тут

Більше про це написано тут

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

Ми можемо легко здійснити опитування за допомогою служби $ interval. ось докладний документ про $ interval
https://docs.angularjs.org/api/ng/service/$interval
Проблема використання $ interval полягає в тому, що якщо ви виконуєте виклик служби $ http або взаємодію із сервером і якщо затримка перевищує $ інтервал часу то перед тим, як ваш один запит завершиться, він почне інший запит.
Рішення:
1. Опитування повинно бути простим статусом, що надходить із сервера, як один біт або полегшений JSON, тому не повинно тривати більше часу, ніж визначений інтервал часу. Також слід правильно визначити час інтервалу, щоб уникнути цієї проблеми.
2. Якимсь чином це все-таки відбувається з будь-якої причини, вам слід перевірити глобальний прапор, який попередній запит закінчив чи ні, перед тим, як надсилати будь-які інші запити. Він пропустить цей інтервал часу, але не надішле запит передчасно.
Крім того, якщо ви хочете встановити порогове значення, яке після будь-якого значення в будь-якому випадку має бути встановлене опитування, ви можете зробити це наступним чином.
Ось робочий приклад. докладно пояснили тут

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;
    }
}]);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.