Как использовать $ scope. $ Watch и $ scope. $ Apply в AngularJS?

1011

Я не понимаю, как использовать $scope.$watch и $scope.$apply. Официальная документация не помогает.

Что я не понимаю конкретно:

  • Связаны ли они с DOM?
  • Как обновить изменения DOM в модели?
  • Какая точка соединения между ними?

Я попробовал этот учебник, но он понимает $watch и $apply как должное.

Что делают $apply и $watch, и как их использовать соответствующим образом?

Теги:
angularjs-scope

7 ответов

1573
Лучший ответ

Вам нужно знать, как работает AngularJS, чтобы понять это.

Цикл дайджеста и $scope

Прежде всего, AngularJS определяет концепцию так называемого цикла дайджеста. Этот цикл можно рассматривать как цикл, в течение которого AngularJS проверяет, есть ли какие-либо изменения для всех переменных просмотренных всеми $scope s. Поэтому, если у вас есть $scope.myVar, определенный в вашем контроллере, и эта переменная была отмечена для просмотра, вы неявно говорите, что AngularJS контролирует изменения на myVar на каждой итерации цикла.

Естественным следственным вопросом будет следующее: Наблюдается ли все привязанное к $scope? К счастью, нет. Если вы будете следить за изменениями в каждом объекте в $scope, то быстро цикл сбора данных потребует времени для оценки, и вы быстро столкнетесь с проблемами производительности. Вот почему команда AngularJS дала нам два способа объявления некоторой переменной $scope в качестве наблюдаемой (см. Ниже).

$watch помогает прослушивать изменения $scope

Существует два способа объявления переменной $scope.

  • Используя его в шаблоне с помощью выражения <span>{{myVar}}</span>
  • Добавив его вручную через службу $watch

Объявление 1) Это самый распространенный сценарий, и я уверен, что вы его видели раньше, но вы не знали, что это создало часы в фоновом режиме. Да, это было! Использование директив AngularJS (например, ng-repeat) также может создавать неявные часы.

Объявление 2) Вот как вы создаете свои собственные часы. Служба $watch помогает вам запускать некоторый код, когда какое-то значение, связанное с $scope, изменилось. Он редко используется, но иногда помогает. Например, если вы хотите запускать некоторый код каждый раз, когда меняются изменения myVar, вы можете сделать следующее:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$apply позволяет интегрировать изменения с циклом дайджеста

Вы можете думать о функции $apply как о механизме интеграции. Видите ли, каждый раз, когда вы меняете какую-либо переменную наблюдаемую, прикрепленную к объекту $scope, AngularJS будет знать, что это произошло. Это связано с тем, что AngularJS уже знал, как контролировать эти изменения. Поэтому, если это происходит в коде, управляемом каркасом, цикл дайджест будет продолжаться.

Однако иногда вы хотите изменить какое-то значение вне мира AngularJS и увидеть, как изменения распространяются нормально. Рассмотрим это: у вас есть значение $scope.myVar, которое будет изменено в обработчике jQuery $.ajax(). Это произойдет в какой-то момент в будущем. AngularJS не может дождаться, когда это произойдет, так как ему не было предложено ждать jQuery.

Чтобы решить эту проблему, был введен $apply. Это позволяет вам начать цикл пищеварения явно. Однако вы должны использовать это только для переноса некоторых данных в AngularJS (интеграция с другими фреймворками), но никогда не используйте этот метод в сочетании с обычным кодом AngularJS, так как AngularJS будет выдавать ошибку.

Как все это связано с DOM?

Хорошо, вам следует по-настоящему следовать учебнику, теперь, когда вы все это знаете. Цикл дайджест будет следить за тем, чтобы пользовательский интерфейс и код JavaScript синхронизировались, оценивая каждого наблюдателя, прикрепленного ко всем $scope, пока ничего не меняется. Если в цикле дайджеста больше не происходит изменений, то он считается завершенным.

Вы можете прикреплять объекты к объекту $scope либо явно в контроллере, либо объявляя их в форме {{expression}} непосредственно в представлении.

Надеюсь, это поможет прояснить некоторые базовые знания обо всем этом.

Дальнейшие чтения:

  • 55
    «Угловая проверка, есть ли какие-либо изменения во всех переменных, прикрепленных ко всем $ scopes» - я не думаю, что это правильно. Я считаю, что Angular only (грязно) проверяет свойства $ scope, для которых настроены $ watches (обратите внимание, что использование {{}} в представлении автоматически создаст $ watch). См. Также раздел «Вопросы производительности Scope $ watch» на странице Scope .
  • 5
    Это может быть так. Я постараюсь найти время, чтобы прочитать об этом больше и отредактировать свой ответ.
Показать ещё 5 комментариев
152

В AngularJS мы обновляем наши модели, а наши представления/шаблоны обновляют DOM "автоматически" (через встроенные или настраиваемые директивы).

$apply и $watch, оба являются методами Scope, не связаны с DOM.

Страница Concepts (раздел "Время выполнения" ) имеет довольно хорошее объяснение цикла $digest, $apply, очереди $evalAsync и $watch список. Вот изображение, которое сопровождает текст:

Изображение 3634

Независимо от того, какой код имеет доступ к области – обычно контроллеры и директивы (их функции связи и/или их контроллеры) – может настроить "watchExpression", которое AngularJS будет оценивать с этой областью. Эта оценка происходит всякий раз, когда AngularJS вводит цикл $digest (в частности, цикл "$ watch list" ). Вы можете посмотреть отдельные свойства области, вы можете определить функцию для просмотра двух свойств вместе, вы можете посмотреть длину массива и т.д.

Когда вещи происходят "внутри AngularJS" – например, вы вводите текстовое поле с включенной двунаправленной привязкой по AngularJS (т.е. использует ng-модель), срабатывает обратный вызов $http и т.д. – $apply уже вызывается, поэтому мы находимся внутри прямоугольника "AngularJS" на рисунке выше. Все watchExpression будут оцениваться (возможно, более одного раза, пока не будут обнаружены дальнейшие изменения).

Когда вещи случаются "вне AngularJS" – например, вы использовали bind() в директиве, а затем это событие срабатывает, в результате чего вы вызываете обратный вызов или запускаете зарегистрированный обратный вызов jQuery – мы все еще находимся в прямоугольнике "Родной". Если код обратного вызова изменяет все, что наблюдают любые часы $, вызовите $apply, чтобы попасть в прямоугольник AngularJS, заставив цикл $digest работать, и, следовательно, AngularJS заметит изменение и сделает свою магию.

  • 4
    Я понимаю идею, но я не понимаю, как данные передаются. У меня есть модель, которая является объектом с большим количеством данных, я использую некоторые из них для манипулирования DOM. тогда что-то из этого меняется. Как поместить измененные данные в нужное место в модели? В примере, который я использовал, он выполняет манипуляцию и в конце концов просто использует scope.$apply(scope.model) , я не понимаю, какие данные передаются и как они передаются в нужное место в модели?
  • 5
    Волшебная передача данных не происходит. Обычно с приложениями Angular следует менять модели Angular, которые затем управляют обновлениями вида / DOM. Если вы обновляете DOM вне Angular, вам придется вручную обновлять модели. scope.$apply(scope.model) просто оценит scope.model как scope.model выражение, а затем scope.model цикл $ digest. В статье, на которую вы ссылаетесь, вероятно, будет полезен scope. $ Apply scope.$apply() , так как модель уже отслеживается. Функция stop () обновляет модель (я считаю, что toUpdate - это ссылка на scope.model), а затем вызывается $ apply.
52

Этот блог был охвачен всем, что создает примеры и понятные объяснения.

Функции AngularJS $scope $watch(), $digest() и $apply() являются одними из центральных функций в AngularJS. Понимание $watch(), $digest() и $apply() необходимо для понимания AngularJS.

Когда вы создаете привязку данных где-то в своем представлении к переменной объекта $scope, AngularJS создает "часы" внутри. Часы означают, что AngularJS наблюдает за изменениями в переменной на $scope object. Структура - это "просмотр" переменной. Часы создаются с помощью функции $scope.$watch(), которую я расскажу позже в этом тексте.

В ключевых точках вашего приложения AngularJS вызывает функцию $scope.$digest(). Эта функция выполняет итерацию через все часы и проверяет, изменилась ли какая-либо из наблюдаемых переменных. Если изменилась наблюдаемая переменная, вызывается соответствующая функция прослушивателя. Функция прослушивателя выполняет любую работу, которую он должен выполнять, например, изменяя текст HTML, чтобы отразить новое значение наблюдаемой переменной. Таким образом, функция $digest() - это то, что инициирует привязку данных к обновлению.

В большинстве случаев AngularJS будет вызывать $scope. Функции $watch() и $scope.$digest() для вас, но в некоторых ситуациях вам, возможно, придется называть их самостоятельно. Поэтому очень хорошо знать, как они работают.

Функция $scope.$apply() используется для выполнения некоторого кода, а затем вызывает $scope.$digest() после этого, поэтому все часы проверяются и вызываются соответствующие функции прослушивателя часов. Функция $apply() полезна при интеграции AngularJS с другим кодом.

Я подробно расскажу о функциях $watch(), $digest() и $apply() в оставшейся части этого текста.

$часы()

Функция $scope.watch() создает часы некоторой переменной. Когда вы регистрируете часы, вы передаете две функции в качестве параметров функции $watch():

  • Функция значения
  • Функция прослушивателя

Вот пример:

$scope.$watch(function() {},
              function() {}
             );

Первая функция - это функция значения, а вторая функция - функция слушателя.

Функция value должна вернуть значение, которое просматривается. AngularJS может затем проверить значение, возвращенное против значения, которое функция часов вернула в последний раз. Таким образом, AngularJS может определить, изменилось ли значение. Вот пример:

$scope.$watch(function(scope) { return scope.data.myVar },
              function() {}
             );

В этом примере функция valule возвращает переменную $scope scope.data.myVar. Если значение этой переменной изменится, будет возвращено другое значение, и функция AngularJS вызовет функцию слушателя.

Обратите внимание, как функция значения принимает область как параметр (без $в имени). С помощью этого параметра функция значения может получить доступ к $scope и ее переменным. Функция значения также может смотреть глобальные переменные, если вам это нужно, но чаще всего вы будете смотреть переменную $scope.

Функция слушателя должна делать все, что нужно, если значение изменилось. Возможно, вам нужно изменить содержимое другой переменной или установить содержимое элемента HTML или что-то еще. Вот пример:

$scope.$watch(function(scope) { return scope.data.myVar },
              function(newValue, oldValue) {
                  document.getElementById("").innerHTML =
                      "" + newValue + "";
              }
             );

В этом примере внутренний HTML-код элемента HTML добавляется к новому значению переменной, встроенному в элемент b, который делает значение полужирным. Конечно, вы могли бы сделать это, используя код {{ data.myVar }, но это всего лишь пример того, что вы можете сделать внутри функции слушателя.

$переваривать()

Функция $scope.$digest() выполняет итерацию через все часы в $scope object и ее дочерние объекты $scope (если они есть). Когда $digest() выполняет итерацию по часам, она вызывает функцию значения для каждого часового механизма. Если значение, возвращаемое функцией значения, отличается от значения, которое оно возвращало в последний раз, когда оно было вызвано, вызывается функция слушателя для этих часов.

Функция $digest() вызывается всякий раз, когда AngularJS считает, что это необходимо. Например, после выполнения обработчика нажатия кнопки или после вызова AJAX (после выполнения функции обратного вызова done()/fail()).

Вы можете столкнуться с некоторыми угловыми случаями, когда AngularJS не вызывает функцию $digest() для вас. Обычно вы обнаружите это, заметив, что привязки данных не обновляют отображаемые значения. В этом случае вызовите $scope.$digest(), и он должен работать. Или, вы можете использовать $scope.$apply() вместо этого, что я объясню в следующем разделе.

$применить()

Функция $scope.$apply() принимает функцию как параметр, который выполняется, а после этого $scope.$digest() вызывается внутри. Это облегчит вам проверку всех часов, и, таким образом, все привязки данных обновляются. Вот пример $apply():

$scope.$apply(function() {
    $scope.data.myVar = "Another value";
});

Функция, переданная функции $apply() в качестве параметра, изменит значение $scope.data.myVar. Когда функция выходит из AngularJS вызовет функцию $scope.$digest(), чтобы все часы были проверены на изменение наблюдаемых значений.

Пример

Чтобы проиллюстрировать, как работают $watch(), $digest() и $apply(), посмотрите на этот пример:

<div ng-controller="myController">
    {{data.time}}

    <br/>
    <button ng-click="updateTime()">update time - ng-click</button>
    <button id="updateTimeButton"  >update time</button>
</div>


<script>
    var module       = angular.module("myapp", []);
    var myController1 = module.controller("myController", function($scope) {

        $scope.data = { time : new Date() };

        $scope.updateTime = function() {
            $scope.data.time = new Date();
        }

        document.getElementById("updateTimeButton")
                .addEventListener('click', function() {
            console.log("update time clicked");
            $scope.data.time = new Date();
        });
    });
</script>

его пример связывает переменную $scope.data.time с директивой интерполяции, которая объединяет значение переменной в HTML-страницу. Это связывание создает часы внутри $scope.data.time variable.

Пример также содержит две кнопки. Первая кнопка имеет при себе ng-click прослушиватель. Когда эта кнопка нажата, вызывается функция $scope.updateTime(), и после этого AngularJS вызывает $scope.$digest(), чтобы обновления данных были обновлены.

Вторая кнопка получает стандартный прослушиватель событий JavaScript, прикрепленный к ней изнутри функции контроллера. Когда нажимается вторая кнопка, выполняется функция слушателя. Как вы можете видеть, функции слушателя для обеих кнопок делают почти то же самое, но когда вызывается вторая функция прослушивания кнопки, привязка данных не обновляется. Это связано с тем, что $scope.$digest() не вызывается после выполнения второго прослушивателя событий кнопки. Таким образом, если вы нажмете вторую кнопку, время будет обновлено в переменной $scope.data.time, но новое время никогда не будет отображаться.

Чтобы исправить это, мы можем добавить вызов $scope.$digest() в последнюю строку прослушивателя событий кнопки, например:

document.getElementById("updateTimeButton")
        .addEventListener('click', function() {
    console.log("update time clicked");
    $scope.data.time = new Date();
    $scope.$digest();
});

Вместо вызова $digest() внутри функции прослушивания кнопок вы также могли бы использовать функцию $apply() следующим образом:

document.getElementById("updateTimeButton")
        .addEventListener('click', function() {
    $scope.$apply(function() {
        console.log("update time clicked");
        $scope.data.time = new Date();
    });
});

Обратите внимание, как функция $scope.$apply() вызывается изнутри прослушивателя событий кнопки и как выполняется обновление переменной $scope.data.time внутри функции, переданной как параметр функции $apply(). Когда вызов функции $apply() завершает AngularJS вызывает $digest() внутренне, поэтому все привязки данных обновляются.

36

AngularJS расширяет этот цикл событий, создавая что-то, называемое AngularJS context.

$часы()

Каждый раз, когда вы связываете что-то в пользовательском интерфейсе, вы вставляете $watch в список $watch.

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

Здесь мы имеем $scope.user, связанный с первым входом, и мы имеем $scope.pass, который привязан ко второму. Сделав это, мы добавим два $watch es в список $watch.

Когда наш шаблон загружается, AKA на этапе связывания, компилятор будет искать каждую директиву и создает все $watch es, которые необходимы.

AngularJS предоставляет $watch, $watchcollection и $watch(true). Ниже приведена аккуратная диаграмма, объясняющая все три, взятые из глубины наблюдения.

Изображение 3635

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest loop

Когда браузер получает событие, которое может управляться контекстом AngularJS, цикл $digest будет запущен. Этот цикл выполнен из двух меньших петель. Один обрабатывает очередь $evalAsync, а другой обрабатывает $watch list. $digest будет проходить через список $watch, который мы имеем

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

Здесь у нас есть только один $watch, потому что ng-click не создает никаких часов.

Нажимаем кнопку.

  • Браузер получает событие, которое войдет в контекст AngularJS
  • цикл $digest будет запущен и будет запрашивать каждые $watch для изменений.
  • Так как $watch, который следил за изменениями в $scope.name сообщает об изменении, это заставит другой цикл $digest.
  • Новый цикл ничего не сообщает.
  • Браузер возвращает управление, и он обновит DOM отражающее новое значение $scope.name
  • Здесь важно, чтобы событие КАЖДЫЙ, входящий в контекст AngularJS, запускало цикл $digest. Это означает, что каждый раз, когда мы пишем письмо во входе, цикл будет проверять каждый $watch на этой странице.

$применяются()

Если вы вызываете $apply при запуске события, он будет проходить через angular -контекст, но если вы его не назовете, он выйдет за его пределы. Это так просто. $apply вызовет цикл $digest() внутри, и он будет перебирать все часы, чтобы обновить DOM с обновленным значением.

Метод $apply() вызывает наблюдателей во всей цепочке $scope, тогда как метод $digest() будет запускать только наблюдателей на текущем $scope и его children. Если ни один из вышеперечисленных объектов $scope не должен знать о локальных изменениях, вы можете использовать $digest().

17

Есть $watchGroup и $watchCollection. В частности, $watchGroup действительно полезно, если вы хотите вызвать функцию для обновления объекта, который имеет несколько свойств в представлении, не являющемся объектом dom, например. другой вид в холсте, webGL или запрос сервера. Здесь документация ссылка.

  • 0
    Я бы прокомментировал $watchCollection но я вижу, что вы уже сделали. Вот документация об этом с сайта AngularJS. Они обеспечивают очень хорошее визуальное представление о глубине $watch . Обратите внимание, что информация находится близко к нижней части страницы.
15

Я нашел очень глубокие видеоролики, которые охватывают $watch, $apply, $digest и переваривают циклы в:

Ниже приведены несколько слайдов, используемых в этих видео для объяснения понятий (на всякий случай, если вышеупомянутые ссылки удалены/не работают).

Изображение 3636

В приведенном выше изображении "$ scope.c" не просматривается, поскольку он не используется ни в одной из привязок данных (в разметке). Остальные два ($scope.a и $scope.b) будут просмотрены.

Изображение 3637

Из приведенного выше изображения. На основе соответствующего события браузера AngularJS захватывает событие, выполняет цикл дайджеста (проходит через все часы для изменений), выполняет функции часов и обновляет DOM. Если нет событий браузера, цикл дайджеста можно запустить вручную с помощью $apply или $digest.

Подробнее о $apply и $digest:

Изображение 3638

10

Просто закончите читать ВСЕ все, скучно и сонно (извините, но это правда). Очень технический, углубленный, подробный и сухой. Почему я пишу? Поскольку AngularJS является массивным, множество взаимосвязанных концепций могут превратить любого, кто сходит с ума. Я часто спрашивал себя: неужели я недостаточно умен, чтобы понять их? Нет! Это потому, что мало кто может объяснить технологию в для-dummie language без всех терминов! Хорошо, позвольте мне попробовать:

1) Все они управляются событиями. (я слышу смех, но читаю дальше)

Если вы не знаете, что такое event-driven, подумайте, что вы ставите кнопку на странице, подключите его к функции с помощью "on-click", ожидая чтобы щелкнуть по нему, чтобы вызвать действия, которые вы устанавливаете внутри функция. Или подумайте о "запуске" SQL Server/Oracle.

2) $watch - это "щелчок".

Что особенно важно, так это две функции в качестве параметров, первая дает значение из события, второе принимает значение в рассмотрение...

3) $digest - босс, который неустанно проверяет бла-бла-бла, но хороший босс.

4) $apply дает вам способ, когда вы хотите сделать это вручную, например, с отказоустойчивым (в случае, если щелчок не срабатывает, вы вынуждаете его работать).

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

В ресторане

- WAITERS должны принимать заказы от клиентов, это

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- MANAGER работает, чтобы убедиться, что все официанты бодрствуют, реагируя на любые признаки изменений от клиентов. Это $digest()

- OWNER обладает максимальной способностью управлять всеми по запросу, это $apply()

Ещё вопросы

Сообщество Overcoder
Наверх
Меню