Как работает привязка данных в AngularJS?

Как привязка данных работает в рамках AngularJS?

Я не нашел технических подробностей их сайта. Это более или менее понятно, как это работает, когда данные распространяются из представления в модель. Но как AngularJS отслеживает изменения свойств модели без сеттеров и геттеров?

Я обнаружил, что есть наблюдатели JavaScript, которые могут выполнять эту работу. Но они не поддерживаются в Internet Explorer 6 и Internet Explorer 7. Итак, как AngularJS знает, что я изменил, например, следующее и отразил это изменение в представлении?

myobject.myproperty="new value";

Ответ 1

AngularJS запоминает значение и сравнивает его с предыдущим значением. Это основная грязная проверка. Если есть изменение в значении, то он запускает событие изменения.

Метод $apply(), который вы называете, когда вы переходите из мира без углов в мир AngularJS, вызывает $digest(). Дайджест - просто обычная грязная проверка. Он работает во всех браузерах и полностью предсказуем.

Чтобы контрастировать с грязной проверкой (AngularJS) и сменить слушателей (KnockoutJS и Backbone.js): Хотя проверка грязности может показаться простой и даже неэффективной (я расскажу об этом позже), выясняется, что она семантически корректна все время, в то время как у слушателей изменений есть много странных угловых случаев и нужны такие вещи, как отслеживание зависимостей, чтобы сделать это более семантически правильно. Отслеживание зависимостей KnockoutJS - это умная функция для проблемы, которую не имеет AngularJS.

Проблемы с прослушивателями изменений:

  • Синтаксис является жестоким, поскольку браузеры не поддерживают его изначально. Да, есть прокси, но они не являются семантически правильными во всех случаях, и, конечно, в старых браузерах нет прокси-серверов. Суть в том, что грязная проверка позволяет вам выполнять POJO, тогда как KnockoutJS и Backbone.js заставляют вас наследовать их классы и получать доступ к вашим данным через аксессуры.
  • Изменение коалесценции. Предположим, у вас есть массив элементов. Предположите, что вы хотите добавлять элементы в массив, поскольку вы добавляете цикл, каждый раз, когда вы добавляете, вы запускаете события с изменениями, которые отображают пользовательский интерфейс. Это очень плохо для производительности. Вы хотите обновить интерфейс только один раз, в конце. События изменения слишком мелкие.
  • Смена слушателей немедленно срабатывает на сеттере, что является проблемой, поскольку прослушиватель изменений может дополнительно изменять данные, что вызывает больше изменений событий. Это плохо, так как в вашем стеке у вас может быть несколько изменений, происходящих сразу. Предположим, у вас есть два массива, которые нужно синхронизировать по любой причине. Вы можете добавлять только одно или другое, но каждый раз, когда вы добавляете, вы запускаете событие изменения, которое теперь имеет непоследовательное представление о мире. Это очень похожая проблема для блокировки потоков, которую JavaScript избегает, поскольку каждый обратный вызов выполняется исключительно и завершается. Изменение событий нарушает это, поскольку сеттеры могут иметь далеко идущие последствия, которые не предназначены и не очевидны, что снова создает проблему потока. Оказывается, что вы хотите отложить выполнение слушателя и гарантировать, что только один прослушиватель работает одновременно, поэтому любой код может свободно изменять данные, и он знает, что никакой другой код не работает, пока он делает это.

Как насчет производительности?

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

Люди:

  • Slow - все, что быстрее, чем 50 ms, незаметно для людей и поэтому может считаться "мгновенным".

  • Limited. Вы не можете показать более чем 2000 единиц информации человеку на одной странице. Что-то большее, чем это действительно плохой пользовательский интерфейс, и люди все равно не могут его обработать.

Итак, реальный вопрос заключается в следующем: сколько сравнений вы можете сделать в браузере в 50 ms? Это сложный вопрос для ответа, поскольку многие факторы вступают в игру, но вот тестовый пример: http://jsperf.com/angularjs-digest/6, который создает 10 000 наблюдателей. В современном браузере это занимает всего 6 мкс. В Internet Explorer 8 требуется около 40 мкс. Как вы можете видеть, в наши дни это не проблема даже в медленных браузерах. Существует предостережение: сравнения должны быть простыми в соответствии с временными ограничениями... К сожалению, слишком легко добавить медленное сравнение в AngularJS, поэтому легко создавать медленные приложения, когда вы не знаете, что вы делаем. Но мы надеемся получить ответ, предоставив модуль приборов, который покажет вам, которые являются медленными сравнениями.

Оказывается, видеоигры и графические процессоры используют подход с грязной проверкой, особенно потому, что он согласован. Пока они превышают частоту обновления монитора (обычно 50-60 Гц или каждые 16,6-20 мс), любая производительность по сравнению с этим является пустой тратой, поэтому вам лучше рисовать больше, чем получать FPS выше.

Ответ 2

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

Как заявила Мишко, около 2000 привязок - это то, где вы начинаете видеть проблемы, но в любом случае вы не должны иметь более 2000 единиц информации на странице. Это может быть правдой, но не все данные привязываются к пользователю. После того, как вы начнете создавать какой-либо виджет или сетку данных с двусторонней привязкой, вы можете легко ударить 2000 привязок, не имея плохих ux.

Рассмотрим, например, combobox, где вы можете набирать текст для фильтрации доступных параметров. Такой контроль может иметь ~ 150 элементов и по-прежнему может быть очень полезен. Если у него есть дополнительная функция (например, определенный класс в выбранном в данный момент параметре), вы получаете 3-5 привязок для каждого параметра. Поместите три из этих виджетов на страницу (например, один, чтобы выбрать страну, а другой выбрать город в указанной стране, а третий - выбрать отель), и вы уже где-то между 1000 и 2000 привязками.

Или рассмотрите сетку данных в корпоративном веб-приложении. 50 строк на страницу не являются необоснованными, каждая из которых может содержать 10-20 столбцов. Если вы построите это с помощью ng-повторов и/или получите информацию в некоторых ячейках, которые используют некоторые привязки, вы можете приблизиться к 2000 привязкам только с этой сеткой.

Я считаю, что это проблема огромная при работе с AngularJS, и единственным решением, которое я смог найти до сих пор, является создание виджетов без использования двусторонней привязки, вместо этого используя ngOnce, отмену регистрации наблюдателей и т.д., или создавать директивы, которые строят DOM с манипуляциями jQuery и DOM. Я чувствую, что это побеждает цель использования Angular в первую очередь.

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

TL; DR
Связывание данных может вызвать проблемы с производительностью на сложных страницах.

Ответ 3

Грязная проверка объекта $scope

Angular поддерживает простой массив наблюдателей в объектах $scope. Если вы проверите какой-либо $scope, вы обнаружите, что он содержит массив, называемый $$ watchers.

Каждый наблюдатель - это объект, который содержит помимо всего прочего

  • Выражение, наблюдаемое наблюдателем. Это может быть просто имя атрибута или что-то более сложное.
  • Последнее известное значение выражения. Это можно проверить против текущего вычисленного значения выражения. Если значения отличаются, наблюдатель вызовет функцию и пометит область $как грязную.
  • Функция, которая будет выполняться, если наблюдатель загрязнен.

Как определяются наблюдатели

Существует множество различных способов определения наблюдателя в AngularJS.

  • Вы можете явно $смотреть атрибут в $scope.

    $scope.$watch('person.username', validateUnique);
    
  • Вы можете разместить интерполяцию {{}} в своем шаблоне (наблюдатель будет создан для вас в текущей $scope).

    <p>username: {{person.username}}</p>
    
  • Вы можете задать директиву, такую ​​как ng-model, чтобы определить наблюдателя для вас.

    <input ng-model="person.username" />
    

Цикл $digest проверяет всех наблюдателей на их последнее значение

Когда мы взаимодействуем с angular через нормальные каналы (ng-model, ng-repeat и т.д.), цикл преобразования будет инициироваться директивой.

Цикл дайджеста - это глубина первого пересечения $scope и всех его дочерних. Для каждого объекта $scope мы перебираем массив $$ watchers и оцениваем все выражения. Если новое значение выражения отличается от последнего известного значения, вызывается функция наблюдателя. Эта функция может перекомпилировать часть DOM, пересчитать значение в области $scope, вызвать запрос AJAX, все, что вам нужно.

Каждая область пройдена, и каждое выражение часов оценивается и проверяется на последнее значение.

Если срабатывает наблюдатель, параметр $scope грязный

Если наблюдатель запущен, приложение знает, что что-то изменилось, а область $помечена как грязная.

Функции Watcher могут изменять другие атрибуты в области $scope или в родительской переменной $scope. Если была вызвана одна функция $watcher, мы не можем гарантировать, что наши другие $scopes все еще чисты, и поэтому мы снова выполняем весь цикл дайджест.

Это связано с тем, что angular 1 имеет двустороннюю привязку, поэтому данные могут быть переданы обратно в дерево $scope. Мы можем изменить значение на более высокий $scope, который уже был переварен. Возможно, мы изменим значение на $rootScope.

Если $digest грязный, мы снова выполняем цикл $digest

Мы непрерывно перебираем цикл $digest до тех пор, пока цикл дайджеста не станет чистым (все выражения $watch имеют то же значение, что и в предыдущем цикле), или мы достигнем предела дайджест. По умолчанию этот предел установлен на 10.

Если мы достигнем предела дайджеста angular, это вызовет ошибку в консоли:

10 $digest() iterations reached. Aborting!

Сборник сложный на машине, но простой на разработчика

Как вы можете видеть, каждый раз, когда что-то меняется в приложении angular, angular проверяет каждого наблюдателя в иерархии $scope, чтобы увидеть, как реагировать. Для разработчика это огромный балл производительности, так как теперь вам не нужно писать код проводов без кода, angular будет просто замечать, изменилось ли значение и сделать resto приложения совместимым с изменением.

С точки зрения машины, хотя это дико неэффективно и замедлит наше приложение, если мы создадим слишком много наблюдателей. Misko процитировала цифру около 4000 наблюдателей, прежде чем ваше приложение будет чувствовать себя медленным в старых браузерах.

Этот предел легко достигнут, если вы, например, повторите повтор над большим массивом JSON. Вы можете смягчить это, используя функции, такие как одноразовая привязка для компиляции шаблона без создания наблюдателей.

Как избежать создания слишком большого количества наблюдателей

Каждый раз, когда ваш пользователь взаимодействует с вашим приложением, каждый наблюдатель в вашем приложении будет оцениваться хотя бы один раз. Большая часть оптимизации приложения angular уменьшает количество наблюдателей в вашем дереве $scope. Один простой способ сделать это - с привязкой времени.

Если у вас есть данные, которые редко меняются, вы можете привязать их только один раз, используя синтаксис:: так:

<p>{{::person.username}}</p>

или

<p ng-bind="::person.username"></p>

Связывание будет инициироваться только при визуализации содержащего шаблона и данных, загружаемых в область $scope.

Это особенно важно, если у вас есть ng-repeat со многими элементами.

<div ng-repeat="person in people track by username">
  {{::person.username}}
</div>

Ответ 4

Это мое основное понимание. Возможно, это неправильно!

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

В обычной разработке синтаксис привязки данных в HTML говорит компилятору AngularJS о создании часов для вас, а методы контроллера уже выполняются внутри $apply. Поэтому разработчику приложения все прозрачно.

Ответ 5

Я подумал об этом сам на некоторое время. Без сеттеров, как AngularJS уведомляет об изменениях объекта $scope? Опрашивает ли они их?

Что это на самом деле делает это: любое "нормальное" место, которое вы изменили модель, уже вызывается из кишки AngularJS, поэтому оно автоматически вызывает $apply для вас после запуска вашего кода. Скажем, у вашего контроллера есть метод, который подключается к ng-click для некоторого элемента. Поскольку AngularJS соединяет вызов этого метода для вас, он имеет возможность сделать $apply в соответствующем месте. Аналогично для выражений, которые появляются прямо в представлениях, они выполняются AngularJS, поэтому он имеет $apply.

Когда в документации говорится о необходимости вызывать $apply вручную для кода вне AngularJS, он говорит о коде, который при запуске не связан с самим AngularJS в стеке вызовов.

Ответ 6

Объяснение с картинками:

Для привязки данных требуется отображение

Ссылка в области не является ссылкой в ​​шаблоне. Когда вы привязываете данные к двум объектам, вам нужен третий, который прослушивает первый и меняет другой.

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

Здесь, когда вы изменяете <input>, вы касаетесь data-ref3. И классический механизм привязки данных сменит data-ref4. Итак, как будут перемещаться другие выражения {{data}}?

События приводят к $digest()

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

Angular поддерживает oldValue и newValue каждого привязки. И после каждого события Angular знаменитый цикл $digest() проверяет WatchList, чтобы увидеть, что-то изменилось. Эти события Angular завершены ng-click, ng-change, $http завершены... $digest() будет зацикливаться до тех пор, пока любой oldValue отличается от newValue.

На предыдущем снимке он заметит, что данные ref1 и data-ref2 изменились.

Выводы

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

Другим моментом является то, что вы можете легко понять влияние глубокой простой привязки к памяти и процессору. Надеемся, что рабочие столы достаточно полны, чтобы справиться с этим. Мобильные телефоны не так сильны.

Ответ 7

Очевидно, что нет периодической проверки Scope, есть ли какие-либо изменения в Объектах, прикрепленных к ней. Наблюдаются не все объекты, привязанные к области. Область прототипа поддерживает $$ наблюдателей. Scope выполняет только итерацию через $$watchers, когда вызывается $digest.

Angular добавляет наблюдателя к $$ наблюдателям для каждого из этих

  • {{expression}} - В ваших шаблонах (и где-нибудь еще, где есть выражение) или когда мы определяем ng-модель.
  • $scope. $watch ('expression/function). В вашем JavaScript мы можем просто прикрепить объект области для angular для просмотра.

$watch функция принимает три параметра:

  • Первая - это функция наблюдателя, которая просто возвращает объект, или мы можем просто добавить выражение.

  • Вторая - это функция прослушивателя, которая будет вызываться при изменении объекта. В этой функции будут реализованы все такие вещи, как изменения DOM.

  • Третий - необязательный параметр, который принимает логическое значение. Если его истина, angular глубоко смотрит объект, и если его false angular просто выполняет ссылку, наблюдающую за объектом.   Неверная реализация $watch выглядит так:

Scope.prototype.$watch = function(watchFn, listenerFn) {
   var watcher = {
       watchFn: watchFn,
       listenerFn: listenerFn || function() { },
       last: initWatchVal  // initWatchVal is typically undefined
   };
   this.$$watchers.push(watcher); // pushing the Watcher Object to Watchers  
};

В angular есть интересная вещь, называемая Digest Cycle. Цикл $digest начинается в результате вызова $scope. $Digest(). Предположим, что вы изменили модель $scope в функции обработчика с помощью директивы ng-click. В этом случае AngularJS автоматически запускает цикл $digest, вызывая $digest(). В дополнение к ng-click есть несколько других встроенных директив/служб, которые позволяют вам изменять модели (например, ng-model, $timeout и т.д.), и автоматически запускает цикл $digest. Грубая реализация $digest выглядит следующим образом.

Scope.prototype.$digest = function() {
      var dirty;
      do {
          dirty = this.$$digestOnce();
      } while (dirty);
}
Scope.prototype.$$digestOnce = function() {
   var self = this;
   var newValue, oldValue, dirty;
   _.forEach(this.$$watchers, function(watcher) {
          newValue = watcher.watchFn(self);
          oldValue = watcher.last;   // It just remembers the last value for dirty checking
          if (newValue !== oldValue) { //Dirty checking of References 
   // For Deep checking the object , code of Value     
   // based checking of Object should be implemented here
             watcher.last = newValue;
             watcher.listenerFn(newValue,
                  (oldValue === initWatchVal ? newValue : oldValue),
                   self);
          dirty = true;
          }
     });
   return dirty;
 };

Если мы используем функцию JavaScripts setTimeout() для обновления модели области видимости, angular не имеет способа узнать, что вы можете изменить. В этом случае его ответственность за вызов $apply() выполняется вручную, что вызывает цикл $digest. Аналогично, если у вас есть директива, которая устанавливает прослушиватель событий DOM и меняет некоторые модели внутри функции обработчика, вам необходимо вызвать $apply(), чтобы гарантировать, что изменения вступят в силу. Большая идея $apply заключается в том, что мы можем выполнить какой-то код, который не знает о Angular, этот код все еще может изменить ситуацию в области. Если мы применим этот код в $apply, он позаботится о вызове $digest(). Неверная реализация $apply().

Scope.prototype.$apply = function(expr) {
       try {
         return this.$eval(expr); //Evaluating code in the context of Scope
       } finally {
         this.$digest();
       }
};

Ответ 8

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

$watch(): -

Эта функция используется для наблюдения за изменениями в переменной $scope. Он принимает три параметра: выражение, слушатель и объект равенства, где слушатель и объект равенства являются необязательными параметрами.

$digest() -

Эта функция выполняет итерацию через все часы в объекте $scope, и его дочерние объекты $scope    (если он есть). Когда $digest() выполняет итерацию над часами он проверяет, имеет ли значение выражения выражение изменилось. Если значение изменилось, AngularJS вызывает слушателя с новое значение и старое значение. Вызывается функция $digest() когда AngularJS думает, что это необходимо. Например, после кнопки клик или после вызова AJAX. У вас могут быть случаи, когда AngularJS не вызывает функцию $digest() для вас. В этом случае вы должны Назовите это сами.

$apply() -

Angular автоматически обновлять только те изменения модели, которые внутри контекста AngularJS. Когда вы меняете любую модель за пределами контекст Angular (например, события DOM браузера, setTimeout, XHR или третий партийных библиотек), то вам необходимо сообщить Angular об изменениях вызов $apply() вручную. Когда заканчивается вызов функции $apply() AngularJS вызывает $digest() внутренне, поэтому все привязки данных обновлено.

Ответ 9

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

Например, если у модели было что-то вроде:

$scope.model.people.name

Управляющий вход формы:

<input type="text" name="namePeople" model="model.people.name">

Таким образом, если вы измените значение контроллера объекта, это будет автоматически отражено в представлении.

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

Ответ 10

AngularJs поддерживает Двустороннюю привязку данных.
Значит, вы можете получить доступ к данным Вид → Контроллер и Контроллер → Просмотр

Для примера

1)

// If $scope have some value in Controller. 
$scope.name = "Peter";

// HTML
<div> {{ name }} </div>

O/P

Peter

Вы можете привязать данные в ng-model Нравится: -
2)

<input ng-model="name" />

<div> {{ name }} </div>

Здесь, в приведенном выше примере, какой бы входной пользователь дал, он будет отображаться в теге <div>.

Если вы хотите привязать входные данные от html к контроллеру: -
3)

<form name="myForm" ng-submit="registration()">
   <label> Name </lbel>
   <input ng-model="name" />
</form>

Здесь, если вы хотите использовать вход name в контроллере, тогда

$scope.name = {};

$scope.registration = function() {
   console.log("You will get the name here ", $scope.name);
};

ng-model связывает наше представление и отображает его в выражении {{ }}.
ng-model - это данные, которые отображаются пользователю в представлении и с которым взаимодействует пользователь.
Таким образом, легко привязывать данные в AngularJs.

Ответ 11

  • Односторонняя привязка данных - это подход, в котором значение берется из модели данных и вставляется в элемент HTML. Невозможно обновить модель из представления. Он используется в классических системах шаблонов. Эти системы связывают данные только в одном направлении.

  • Связывание данных в приложениях Angular - это автоматическая синхронизация данных между моделью и компонентами представления.

Связывание данных позволяет рассматривать модель в качестве единственного источника истины в вашем приложении. Представление - это проекция модели во все времена. Если модель изменена, представление отражает изменение и наоборот.

Ответ 12

Вот пример привязки данных к AngularJS, используя поле ввода. Я объясню позже

Код HTML

<div ng-app="myApp" ng-controller="myCtrl" class="formInput">
     <input type="text" ng-model="watchInput" Placeholder="type something"/>
     <p>{{watchInput}}</p> 
</div>

Код AngularJS

myApp = angular.module ("myApp", []);
myApp.controller("myCtrl", ["$scope", function($scope){
  //Your Controller code goes here
}]);

Как вы можете видеть в приведенном выше примере, AngularJS использует ng-model для прослушивания и просмотра событий, происходящих на элементах HTML, особенно в input. Когда что-то происходит, сделайте что-нибудь. В нашем случае ng-model привязывается к нашему представлению, используя нотацию уса {{}}. Все, что вводится внутри поля ввода, отображается на экране мгновенно. И что красота привязки данных, используя AngularJS в его простейшей форме.

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

См. рабочий пример здесь Codepen

Ответ 13

Angular.js создает наблюдателя для каждой модели, которую мы создаем. Всякий раз, когда модель изменяется, к модели добавляется класс "ng-dirty", поэтому наблюдатель будет наблюдать за всеми моделями, которые имеют класс "ng-dirty", и обновлять их значения в контроллере и наоборот.