Сделать AngularJS пропускать цикл дайджеста, если $http.get() не привел к появлению новых данных

Сейчас я просматриваю сервер для проверки новых данных, а затем обновляю модель в приложении AngularJS. Он примерно то, что я делаю:

setInterval(function () {
    $http.get('data.json').then(function (result) {
        if (result.data.length > 0) {
          // if data, update model here
        } else {
          // nothing has changed, but AngularJS will still start the digest cycle
        }
    });
}, 5000);

Это отлично работает, но большинство запросов не приведут к новым изменениям данных или данных, но служба $http действительно не знает/не заботится и все равно вызовет цикл дайджеста. Я считаю, что это не нужно (поскольку цикл дайджеста является одной из самых тяжелых операций в приложении). Есть ли способ по-прежнему использовать $http, но каким-то образом пропускать дайджест, если ничего не изменилось?

Одним из решений было бы не использовать $http, а jQuery, а затем вызвать $apply, чтобы Angular знал, что модель изменилась:

setInterval(function () {
    $.get('data.json', function (dataList) {
        if (dataList.length > 0) {
            // if data, update model
            $scope.value = dataList[0].value + ' ' + new Date();

            // notify angular manually that the model has changed.
            $rootScope.$apply();
        }
    });
}, 5000);

Пока это работает, я не уверен, что это хорошая идея. Я по-прежнему хотел бы использовать чистый Angular, если это возможно.

У кого-нибудь есть предложения по улучшению вышеприведенного подхода или более элегантного решения?

P.S. Причина, по которой я использую setInterval вместо $timeout, состоит в том, что $timeout также запускает цикл дайджеста, который в этом случае был бы лишним и только добавлял бы к "проблеме".

Ответы

Ответ 1

Веб-сокеты, по-видимому, будут самым элегантным решением здесь. Таким образом, вам не нужно опросить сервер. Сервер может сообщить ваше приложение, когда данные или что-то изменилось.

Ответ 2

  • Решение, предоставленное AngularJS @doc

AngularJS рекомендует использовать трюк PERF, который связывает несколько ответов $http в одном $digest через $httpProvider. Это опять же, не устраняет проблему, это просто успокаивающее:)

$httpProvider.useApplyAsync(true)

  1. Сохранение решения $$ watchers - рискованное и не масштабируемое

Во-первых, принятое решение не масштабируемо - вы не собираетесь делать этот трюк $watchers на 100K строках проекта JS-кода - это не может быть и речи.

Во-вторых, даже если проект мал, он довольно рискованно! Что происходит, например, если приходит еще один вызов ajax, который действительно нуждается в этих наблюдателях?


  1. Другое (возможное) рискованное решение

Единственная альтернатива для достижения этого без изменения кода AngularJS заключается в том, чтобы установить $rootScope. $$ phase в true или '$ digest', сделать $http-вызов и вернуть $rootScope. $$ phase to null.

 $rootScope.$$phase = true;
 $http({...})
   .then(successcb, failurecb)
   .finally(function () {
       $rootScope.$$phase = null;            
   });

Риски

1) другие вызовы ajax могут попытаться сделать то же самое → они должны быть синхронизированы с помощью службы ajax для обертывания (более $http)

2) пользователь может инициировать действия пользовательского интерфейса между ними, действия, которые изменят фазу $$ до нуля, и когда вызов ajax вернется и все еще вызовет $digest

Решение появилось после сканирования исходного кода AngularJS - вот строка, которая сохраняет ситуацию: https://github.com/angular/angular.js/blob/e5e0884eaf2a37e4588d917e008e45f5b3ed4479/src/ng/http.js#L1272


  1. Идеальное решение

Поскольку это проблема, с которой все сталкиваются с AngularJS, я думаю, что ее необходимо систематически решать. Ответы выше не, устраняющие проблему, только пытаются избежать этого. Итак, мы должны создать запрос на растяжение AngularJS, который позволит нам указать через $httpProvider конфигурацию, которая не инициирует дайджест для конкретного запроса $http. Надеюсь, они согласятся, что это нужно как-то решить.

Ответ 3

Вы можете сделать это с помощью этого трюка:

var watchers;

scope.$on('suspend', function () {
  watchers = scope.$$watchers;
  scope.$$watchers = [];
});

scope.$on('resume', function () {
  scope.$$watchers = watchers;
  watchers = null;
});

С помощью этого вы удалите свой объем или верните его в цикл $digest.

Вы должны управлять событиями, чтобы сделать это, конечно.

Обратитесь к этому сообщению:

Удалить и восстановить область из циклов дайджеста

Надеюсь, это поможет!