AngularJS под капотом: $apply() и $digest()

| Пятница, 29 августа, 2014

Метки AngularJS


Когда нужно самому вызывать $apply()?

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

Например, если вы используете не сервис $timeout, а Javascript-функцию setTimeout, чтобы изменить модель, то AngularJS никак не может узнать об этих изменениях. Это тот случай, когда разработчик сам должен прописать вызов функции $apply(), которая инициирует запуск цикла $digest(). Аналогично, если вы пишете директиву, внутри которой реализована функция-событие какого-нибудь DOM-элемента и внутри этой функции изменяются модели, то также нужно вызвать явно $apply(), чтобы изменения произвели нужный эффект.

Рассмотрим пример. Допустим на странице после загрузки через две секунды должно появится сообщение. Реализовали на Javascript так:

Запускаем пример, через две секунды в функции таймера изменяется модель message. Но на представлении это никак не отразилось. Причина в том, что не вызвана явно функция $apply(). Исправим код, как показано ниже:

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

Замечание: Это гипотетический пример, для показа разницы, на самом деле лучше использовать вместо setTimeout, встроенный в AngularJS сервис $timeout, который автоматически неявно вызовет $apply() и об этом не надо заботиться разработчику.

Также обратим внимание, что можно сделать изменения модели и затем вызвать в конце функцию $apply() без аргументов. Так, как это cделано ниже:

$scope.getMessage = function() {
    setTimeout(function() {
        $scope.message = 'Прошло две секунды.'; 
        console.log('message:' + $scope.message); 
        $scope.$apply(); // эта функция запустит $digest 
    }, 2000); 
};

В этом примере используется безаргументная функция и она работает. Но лучше передавать свою функцию в качестве аргумента в $apply(), так как она выполнится внутри блока try … catch, и все исключения будут передаваться в сервис $exceptionHandler.