Есть ли способ наблюдать за изменениями атрибутов, инициируемыми из-за пределов мира AngularJS?

Я пытаюсь понять взаимодействие между миром Angular и не-Angular миром.

Учитывая директиву, которую объявляют следующим образом:

<dir1 id="d1" attr1="100"/>

Если код вне angular изменяет директиву следующим образом:

$("#d1").attr("attr1", 1000);

Как директива может узнать, что один из ее атрибутов изменился?


person Sylvain    schedule 18.06.2013    source источник
comment
Вы создатель указанного кода? Или это внешнее приложение, изменяющее ваш DOM? Scope.$apply может быть тем, что вам нужно. Однако более чем вероятно, что вы захотите передать свои данные коду Angular и позволить Angular управлять изменениями DOM, а не наоборот.   -  person Travis Watson    schedule 18.06.2013


Ответы (2)


Вместо этого было бы лучше внести это изменение в директиву. Если по какой-либо причине это невозможно, то есть несколько вариантов.

Вне приложения получите ссылку на любой элемент DOM в приложении. Используя эту ссылку, вы можете затем получить ссылку на ее область. Вы можете использовать свой элемент с идентификатором d1. Например:

var domElement = document.getElementById('d1');
var scope = angular.element(domElement).scope();

Вот несколько вариантов:

Опция 1

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

scope.myvalue = attrs.attr1;

Затем вы можете изменить значение вне приложения (используя приведенную выше ссылку на область действия), например:

scope.$apply(function(){
    scope.myvalue = 1000;
    console.log('attribute changed');
});

Вот скрипка

Вариант 2

Если представление управляется непосредственно с помощью jQuery, я не знаю ни одного случая использования $observe, $watch или изолирующей области привязки к атрибуту, который будет работать, потому что все они привязываются к самому выражению атрибута только один раз, когда ссылка функция запускается впервые. Изменение значения приведет к сбою этих привязок. Таким образом, вам придется $watch атрибут самого элемента DOM (а не через attrs):

scope.$watch(function(){         
    return $(el).attr('attr1');    // Set a watch on the actual DOM value
}, function(newVal){
    scope.message = newVal;
});

Затем вы можете изменить значение вне приложения (используя приведенную выше ссылку на область действия), например:

scope.$apply(function(){
    $("#d1").attr("attr1",1000);
});

Вот скрипка

person Dan    schedule 18.06.2013
comment
См. plnkr.co/edit/ESrh2v1AXBMKOGncg8TS?p=preview. Я пробовал $apply(), но не могу заставить его работать. - person Sylvain; 18.06.2013
comment
На самом деле это не связано; планк не работает, потому что $observe не срабатывает, даже если атрибут изменен внутри директивы. Причина несколько скрыта в источнике angular: наблюдатель никогда не будет вызван, если данный атрибут не интерполирован. (т. е. если он не содержит скобок {{}}). Попробуйте создать привязку в изолированной области и вместо этого использовать $watch. - person Dan; 18.06.2013
comment
Посмотрите этот новый планк, используя $watch(). Это то, что вы имели в виду? Это тоже не работает. plnkr.co/edit/uSdbISm3WoNSi7e2ODxQ?p=preview - person Sylvain; 19.06.2013
comment
$watch должен быть в переменной, прямо сейчас он находится в буквальном значении «100» (которое не меняется, а только переопределяется). Кроме того, обратите внимание, что ручная проверка не работала ни в одном из планков. Я предлагаю использовать переменную для хранения исходного значения и изменять переменную напрямую с помощью jQuery. Вот демо - person Dan; 19.06.2013
comment
Не могли бы вы тогда сказать, что директива не может быть уведомлена об изменении одного из ее атрибутов в результате манипуляций с DOM? - person Sylvain; 19.06.2013
comment
Это выполнимо, но мне кажется хакерским. Я обновил свой ответ вторым вариантом, чтобы показать, как это можно сделать. - person Dan; 19.06.2013
comment
Спасибо, что нашли время, чтобы проверить все это и придумать этот очень хороший ответ. Вариант 2 наиболее близок к тому, что я пытался продемонстрировать. - person Sylvain; 19.06.2013
comment
использование .scope() работает только при включенном режиме отладки. в большинстве производственных приложений режим отладки будет отключен, и использование .scope() приведет к ошибкам @sh0ber - person trickpatty; 21.01.2016
comment
@PatrickLawler Правильно, режим отладки должен быть включен при использовании scope(). - person Dan; 21.01.2016

Используйте библиотеку веб-компонентов, например x-tags от Mozilla или Polymer от Google. Эта опция работает без ручного вызова $scope.$apply при каждом изменении атрибута.

Я использую x-теги из-за их более широкой поддержки браузерами. При определении нового пользовательского тега (директивы) вы можете установить для параметра lifecycle.attributeChanged функцию обратного вызова, которая будет срабатывать каждый раз при изменении аргумента.

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

Контекст функции обратного вызова (объект this) — это сам элемент. Тот, чей атрибут изменился. Обратный вызов может принимать три аргумента:

  • name — имя атрибута,
  • oldValue и
  • newValue – говорят сами за себя.

Итак, теперь к делу:

Код

Это будет следить за изменениями атрибута:

xtag.register('dir1', {
    lifecycle: {
        attributeChanged: function (attribute, changedFrom, changedTo) {
            // Find the element's scope
            var scope = angular.element(this).scope();

            // Update the scope if our attribute has changed
            scope.$apply(function () {
                if (attribute == 'attr1') scope.style = changedTo;
            });
        }
    }
});

Обратный вызов attributeChanged срабатывает только тогда, когда значения аргументов фактически изменяются. Чтобы получить их начальные значения, вам нужно просканировать лот вручную. Самый простой способ - определить директиву:

myApp.directive('dir1', function () {
    return {
        ... ,
        link: function (scope, element, attributes) {
            scope.attr1 = element[0].getAttribute('attr1');
        }
    };
});
person tomekwi    schedule 20.06.2014