Как игнорировать начальную загрузку при просмотре изменений модели в AngularJS?

184

У меня есть веб-страница, которая служит редактором для одной сущности, которая находится в виде глубокого графика в свойстве $ scope.fieldcontainer. Получив ответ от моего REST API (через $ resource), я добавляю часы в 'fieldcontainer'. Я использую эти часы, чтобы определить, является ли страница / сущность «грязной». Прямо сейчас я отскакиваю кнопку сохранения, но на самом деле я хочу сделать кнопку сохранения невидимой, пока пользователь не загрязнит модель.

То, что я получаю, - это один триггер часов, который, я думаю, происходит, потому что назначение .fieldcontainer = ... происходит сразу после того, как я создаю свои часы. Я думал о том, чтобы просто использовать свойство dirtyCount, чтобы поглотить первоначальную ложную тревогу, но это выглядит очень странно ... и я подумал, что должен быть "угловой идиоматический" способ справиться с этим - я не единственный используя часы, чтобы обнаружить грязную модель.

Вот код, где я установил свои часы:

 $scope.fieldcontainer = Message.get({id: $scope.entityId },
            function(message,headers) {
                $scope.$watch('fieldcontainer',
                    function() {
                        console.log("model is dirty.");
                        if ($scope.visibility.saveButton) {
                            $('#saveMessageButtonRow').effect("bounce", { times:5, direction: 'right' }, 300);
                        }
                    }, true);
            });

Я просто продолжаю думать, что должен быть более чистый способ сделать это, чем защита моего кода "UI dirtying" с помощью "if (dirtyCount> 0)" ...

Кевин Хоффман
источник
Мне также нужно иметь возможность перезагрузить $ scope.fieldcontainer на основе определенных кнопок (например, загрузка предыдущей версии сущности). Для этого мне нужно приостановить часы, перезагрузить, а затем возобновить часы.
Кевин Хоффман
Считаете ли вы, что мой ответ может быть принят как ответ? Те, кому нужно время, чтобы прочитать все до конца, склонны согласиться с тем, что это правильное решение.
trixtur
@trixtur У меня та же проблема ОП, но в моем случае ваш ответ не работает, потому что мое старое значение не работает undefined. У него есть значение по умолчанию, которое необходимо в случае, если в моей модели обновления не придумали всю информацию. Поэтому некоторые значения не меняются, но должны срабатывать.
oliholz
@oliholz ​​Так что вместо проверки на undefined выньте «typeof» и сравните old_fieldcontainer со значением по умолчанию.
trixtur
@KevinHoffman Вы должны отметить ответ MW на правильный ответ для других людей, которые находят этот вопрос. Это гораздо лучшее решение. Спасибо!
Dev01

Ответы:

118

установить флаг непосредственно перед начальной загрузкой,

var initializing = true

и затем, когда первый $ watch сработает, сделайте

$scope.$watch('fieldcontainer', function() {
  if (initializing) {
    $timeout(function() { initializing = false; });
  } else {
    // do whatever you were going to do
  }
});

Флаг будет снят только в конце текущего цикла дайджеста, поэтому следующее изменение не будет заблокировано.

переписан
источник
17
Да, это будет работать, но сравнение старого и нового ценностного подхода, предложенного @MW. и проще, и угловатее идиоматичнее. Можете ли вы обновить принятый ответ?
gerryster
2
Всегда устанавливая oldValue равным newValue при первом срабатывании, было специально добавлено, чтобы избежать необходимости взламывать этот флаг
Чарли Мартин,
2
Ответ MW на самом деле неверен, так как новое значение для старого значения не будет обрабатывать случай, когда старое значение не определено.
Trixtur
1
Для комментаторов: это не божественная модель, ничто не мешает вам поместить «инициализированную» переменную в область декоратора, а затем украсить ею «обычные» часы. В любом случае, это полностью выходит за рамки этого вопроса.
переписано
1
См. Plnkr.co/edit/VrWrHHWwukqnxaEM3J5i для сравнения предложенных методов, как для настройки наблюдателей в обратном вызове .get (), так и для инициализации области.
переписано
483

При первом вызове слушателя старое значение и новое значение будут идентичны. Так что просто сделайте это:

$scope.$watch('fieldcontainer', function(newValue, oldValue) {
  if (newValue !== oldValue) {
    // do whatever you were going to do
  }
});

На самом деле, именно так Angular docs рекомендует обращаться с этим :

После того, как наблюдатель зарегистрирован в области действия, слушатель fn вызывается асинхронно (через $ evalAsync) для инициализации наблюдателя. В редких случаях это нежелательно, потому что слушатель вызывается, когда результат watchExpression не изменился. Чтобы обнаружить этот сценарий в слушателе fn, вы можете сравнить newVal и oldVal. Если эти два значения идентичны (===), то слушатель был вызван из-за инициализации

МВт.
источник
3
этот путь более идиоматичен, чем ответ @
rewritten
25
Это не правильно. Смысл в том, nullчтобы игнорировать изменение от начального загруженного значения, а не игнорировать изменение, когда изменений нет.
переписано
1
Ну, функция слушателя всегда будет срабатывать при создании часов. Это игнорирует тот первый звонок, который, как я полагаю, хотел аскер. Если он специально хочет игнорировать изменение, когда старое значение было нулевым, он, конечно, может сравнить oldValue с нулевым ... но я не думаю, что это то, что он хотел сделать.
МВт.
ОП специально спрашивает о наблюдателях ресурсов (из служб REST). Сначала создаются ресурсы, затем применяются наблюдатели, а затем записываются атрибуты из ответа, и только тогда Angular делает цикл. Пропуск первого цикла с флагом «инициализации» обеспечивает способ применения наблюдателя только к инициализированным ресурсам, но все еще отслеживая изменения с / на null.
переписано
7
Это неправильно, oldValueбудет нулевым на первом уходе.
Кевин С.
42

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

$scope.$watch('fieldcontainer', function (new_fieldcontainer, old_fieldcontainer) {
    if (typeof old_fieldcontainer === 'undefined') return;

    // Other code for handling changed object here.
});

Использование флагов работает, но имеет некоторый запах кода, не правда ли?

trixtur
источник
1
Это путь. В Coffeescript это так же просто, как return unless oldValue?(? - экзистенциальный оператор в CS).
Кевин С.
1
Упорки на слово код пахнут! также проверить бог объект LOL. слишком хорошо.
Мэтью Харвуд
1
Хотя это не принятый ответ, это заставило мой код работать при заполнении объекта из API, и я хочу наблюдать различные состояния сохранения. Очень хорошо.
Лукас Реппе Уэландер
1
Спасибо - это помогло мне
Wand Maker
1
Это замечательно, однако в моем случае я создал экземпляр данных в виде пустого массива перед выполнением вызова AJAX для моего API, поэтому проверьте длину этого вместо типа. Но этот ответ определенно показывает, по крайней мере, самый эффективный метод.
субботняя ночь
4

При первоначальной загрузке текущих значений поле старого значения не определено. Таким образом, приведенный ниже пример поможет вам исключить начальные загрузки.

$scope.$watch('fieldcontainer', 
  function(newValue, oldValue) {
    if (newValue && oldValue && newValue != oldValue) {
      // here what to do
    }
  }), true;
Тахсин Туркоз
источник
Вы, вероятно, хотите использовать! == с тех пор nullи undefinedбудет соответствовать в этой ситуации, или (и '1' == 1т. Д.)
Джейми Пэйт
в общем ты прав. Но в этом случае newValue и oldValue не могут быть такими угловыми значениями из-за предыдущих проверок. Оба значения имеют одинаковый тип еще и потому, что принадлежат одному и тому же полю. Спасибо.
Тахсин Туркоз,
3

Просто действительное состояние нового val:

$scope.$watch('fieldcontainer',function(newVal) {
      if(angular.isDefined(newVal)){
          //Do something
      }
});
Луис Сараза
источник