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

1088

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

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

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

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

Что делать $applyи $watchкак, и как их правильно использовать?

Ильи
источник

Ответы:

1737

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

Цикл дайджеста и объем $

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

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

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

Есть два способа объявления $scopeпеременной как наблюдаемой.

  1. Используя его в своем шаблоне через выражение <span>{{myVar}}</span>
  2. Добавляя его вручную через $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значение, которое будет изменено в $.ajax()обработчике jQuery . Это произойдет в какой-то момент в будущем. AngularJS не может ждать, пока это произойдет, поскольку ему не было приказано ждать на jQuery.

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

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

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

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

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

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

ŁukaszBachman
источник
57
«Угловая проверка, есть ли какие-либо изменения во всех переменных, прикрепленных ко всем $ scopes» - я не думаю, что это правильно. Я считаю, что Angular only (грязно) проверяет свойства $ scope, для которых настроены $ watches (обратите внимание, что использование {{}} в представлении автоматически создаст $ watch). См. Также раздел «Вопросы производительности Scope $ watch» на странице Scope .
Марк Райкок
5
Это может быть так. Я постараюсь найти время, чтобы прочитать об этом больше и отредактировать свой ответ.
Лукаш Бахман
15
@MarkRajcok, ты был прав. Я изменил свой ответ и указал на статью, которая хорошо показывает, как это реализовано.
Лукаш Бахман
3
как насчет этого? (Метод «Контроль как»)
Леандро
2
Использование «Control as» не должно влиять на приведенную выше информацию. Использование this.myVar помещает myVar в область видимости.
Маркус Роделл
161

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

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

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

$ digest loop

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

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

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

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

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

$ Часы ()

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

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

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

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

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

Введите описание изображения здесь

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 петля

Когда браузер получает событие, которое может управляться контекстом 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 не создает никаких часов.

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

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

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

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

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

Thalaivar
источник
18

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

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

Введите описание изображения здесь

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

Введите описание изображения здесь

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

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

Введите описание изображения здесь

user203687
источник
17

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

Здесь ссылка на документацию .

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

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

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

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

2) $ watch "на клике".

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

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

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

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

В ресторане,

- Официанты

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

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

- УПРАВЛЯЮЩИЙ ДЕЛАМИ бегает, чтобы убедиться, что все официанты проснулись, реагируя на любые признаки изменений со стороны клиентов. Это$digest()

- ВЛАДЕЛЕЦ имеет максимальную силу, чтобы водить всех по запросу, это$apply()

Jeb50
источник
2
Это может понять пятилетний ребенок. Я ценю такой ответ. +1
Chris22