$ одновременно наблюдать несколько атрибутов и обратный вызов огня только один раз

Интересно, можно ли выполнить какой-либо обратный вызов только один раз после оценки всех (или только некоторых) атрибутов директивы (без изолированной области). Атрибуты действительно хороши для передачи конфигурации в директиву. Дело в том, что вы можете наблюдать каждый атрибут отдельно и обратный вызов огня несколько раз.

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

HTML

<button ng-click="name='John';surname='Brown'">Change all params</button>
<div person name="{{name}}" surname="{{surname}}"></div>

JS

angular.module('app', []).

directive('person', function() {
  return {
    restrict: 'A',
    link: function($scope, $elem, $attrs) {
        var action = function() {
          $elem.append('name: ' + $attrs.name + '<br/> surname: ' + $attrs.surname+'<br/><br/>');
        }
        $attrs.$observe('name', action);
        $attrs.$observe('surname', action);
    }
 }
});

Plunker здесь.

Таким образом, эффект заключается в том, что после смены имени и фамилии за один клик дважды срабатывает обратный вызов action:

name: 
surname: Brown

name: John
surname: Brown

Итак, вопрос: может ли action быть запущен только один раз с измененными именами и фамилиями?

Ответ 1

Вы можете использовать $watch для оценки пользовательской функции, а не конкретной модели.

то есть.

$scope.$watch(function () {
  return [$attrs.name, $attrs.surname];
}, action, true);

Это будет выполняться во всех циклах $digest, и если $watch обнаруживает возвращаемый массив (или, тем не менее, вы хотите структурировать возвращаемое значение функции), не соответствует старому значению, аргумент обратного вызова $watch будет стрелять. Однако, если вы используете объект как возвращаемое значение, не забудьте оставить значение true для последнего аргумента $watch, чтобы $watch выполнил глубокое сравнение.

Ответ 2

Подчеркивание (или lo-dash) имеет функцию once. Если вы завершаете свою функцию внутри once, вы можете гарантировать, что ваша функция будет вызываться только один раз.

angular.module('app', []).

directive('person', function() {
  return {
    restrict: 'A',
    link: function($scope, $elem, $attrs) {
        var action = function() {
          $elem.append('name: ' + $attrs.name + '<br/> surname: ' + $attrs.surname+'<br/><br/>');
        }
        var once = _.once(action);
        $attrs.$observe('name', once);
        $attrs.$observe('surname', once);
    }
 }
});

Ответ 3

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

Решение @cmw кажется более простым, но производительность может пострадать для большого количества параметров и нескольких фазовых циклов $digest, когда равенство объектов оценивается много раз. Однако я решил принять его ответ.

Ниже вы можете найти мой подход:

angular.module('utils.observeAll', []).

factory('observeAll', ['$rootScope', function($rootScope) {
    return function($attrs, callback) {
        var o = {}, 
            callQueued = false, 
            args = arguments,

            observe = function(attr) {
                $attrs.$observe(attr, function(value) {
                    o[attr] = value;
                    if (!callQueued) {
                        callQueued = true;
                        $rootScope.$evalAsync(function() {
                            var argArr = [];
                            for(var i = 2, max = args.length; i < max; i++) {
                                var attr = args[i];
                                argArr.push(o[attr]);
                            }
                            callback.apply(null, argArr);
                            callQueued = false;
                        });
                    }
                });
            };

        for(var i = 2, max = args.length; i < max; i++) {
            var attr = args[i];
            if ($attrs.$attr[attr])
                observe(attr);
        }
    };
}]);

И вы можете использовать его в своей директиве:

angular.module('app', ['utils.observeAll']).

directive('person', ['observeAll', function(observeAll) {
  return {
    restrict: 'A',
    link: function($scope, $elem, $attrs) {
        var action = function() {
          $elem.append('name: ' + $attrs.name + '<br/> surname: ' + $attrs.surname+'<br/><br/>');
        }
        observeAll($attrs, action, 'name', 'surname');
    }
 }
}]);

Plunker здесь

Ответ 4

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

Я добавил $observ calls для обеих переменных, которые я хотел отслеживать, и привязал их к вызову debounce. Поскольку оба они модифицированы с очень малой разницей времени, оба метода $наблюдают за тем же вызовом функции, который запускается после короткой задержки:

var debounceUpdate = _.debounce(function () {
    setMinAndMaxValue(attrs['minFieldName'], attrs['maxFieldName']);
}, 100);

attrs.$observe('minFieldName', function () {
    debounceUpdate();
});

attrs.$observe('maxFieldName', function () {
    debounceUpdate();
});

Ответ 5

Существует несколько способов решения этой проблемы. Мне очень понравилось решение для отладки. Однако, вот мое решение этой проблемы. Это объединяет все атрибуты в одном отдельном атрибуте и создает JSON-представление интересующих вас атрибутов. Теперь вам просто нужно соблюдать один атрибут и иметь хороший пертур тоже!

Вот вилка исходного plunkr с реализацией: link http://plnkr.co/edit/un3iPL2dfmSn1QJ4zWjQ