Как я могу создать директиву AngularJS для анимации элемента при изменении данных?

В приложении, которое я создаю, у меня есть заголовок, который отображается на экране. Он привязан к свойству в моей области. Я использую ng-bind-html, чтобы упростить создание строки заголовка. HTML выглядит примерно так:

<div class="headline slide-and-fade-up" animate-on-change="headline" ng-bind-html="headline"></div>

animate-change — это директива, которую я пытаюсь написать, чтобы заголовок анимировался при его изменении. Запрошенная анимация предназначена для того, чтобы старая анимация перемещалась вверх и за пределы экрана, а новая перемещалась снизу вверх и занимала место. Я создал совместимую с ng-animate анимацию под названием slide-and-fade-up, которая делает то, что я хочу. Поскольку мне нужно анимировать старое значение, уходящее по мере появления нового, мне нужно продублировать элемент и иметь один со старым значением и один с новым в DOM, а затем оставить старый.

Пока моя директива выглядит так:

angular.module('myApp.directives').directive('animateOnChange', ['$animate',
    function($animate) {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                scope.$watch(attrs.animateOnChange, function(newValue, oldValue) {
                    if (newValue !== undefined && oldValue !== undefined && newValue !== oldValue) {
                        $animate.enter(element.clone(), element.parent(), element);
                        $animate.leave(element);
                    }
                });
            }
        }
    }
]);

У этого есть пара проблем:

  1. Похоже, что клонированный элемент не имеет «живой» директивы, т. е. он существует в DOM, но на самом деле не запускает этот код, что означает, что анимация запускается ровно один раз, а затем никогда больше.
  2. Элемент, который входит, имеет в нем старое значение, а элемент, который уходит, имеет в нем новое значение. Я понятия не имею, почему это так, но в основном это точно наоборот

Как я могу закончить эту директиву, чтобы заставить ее делать то, что я хочу? Я заставил его работать, используя ng-if и наблюдая за изменением данных и включая логическое значение, подождав короткое время, а затем снова включив его. Это сработало, но мне показалось слишком хакерским по сравнению с автономной директивой с $animate хуками. Спасибо за помощь.


person Morinar    schedule 12.06.2014    source источник
comment
Можете ли вы опубликовать ссылку на упрощенный Plunker/Fiddle, который демонстрирует этот код?   -  person Marc Kline    schedule 12.06.2014
comment
Исключительно простой пример, но вот неработающий код, который у меня есть сейчас: codepen.io/nseegmiller/pen/blyxI   -  person Morinar    schedule 12.06.2014


Ответы (2)


Скомпилируйте клонированный контент, который вы передаете в $animate.enter, затем установите для innerHTML исходного элемента значение «oldValue», предоставленное в качестве аргумента вашему $watch непосредственно перед вызовом $animate.leave:

.directive('animateOnChange', function($animate, $compile) {
  var watchers = {};
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      // deregister `$watch`er if one already exists
      watchers[scope.$id] && watchers[scope.$id]();  
      watchers[scope.$id] = scope.$watch(attrs.animateOnChange, function(newValue, oldValue) {          
        if (newValue !== oldValue) {
          $animate.enter($compile(element.clone())(scope), element.parent(), element);
          element.html(oldValue);
          $animate.leave(element); 
        }
      });
    }
  }
})

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

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

Вилка вашего JS Bin

person Marc Kline    schedule 13.06.2014

Функция ссылки запускается при первоначальном размещении элемента на странице на этапе компиляции.

$animate.leave() по завершении удалит оставшийся элемент из DOM, и это тот элемент, к которому вы привязали свое поведение. Вероятно, поэтому это происходит только один раз.

Для начала вам нужно будет ввести переменную для хранения «текущего» элемента, а затем управлять им при каждом переходе.

Теперь проблема в том, что вызов element.clone() проблематичен. Все достоинства Angular не привязываются к нему по волшебству. Это выполняется во время фазы наблюдения, что означает, что область (модель данных) изменилась, но DOM, возможно, еще не обновился. Таким образом, вы клонируете элемент до того, как Angular обновит его, и клон становится «глупым». Это тот, со старым значением, который вы видите на входе.

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

Возможно, вам придется взять на себя полную ответственность за рендеринг контента, а не использовать ng-bind-html. Просто возьмите элементы, которые вы создаете, и вызовите element.html(newValue), который у вас уже есть, так как вы наблюдаете за этим выражением. Или вы можете сделать так, чтобы клонированный (тупой) элемент был тем, который уходит, и разрешить вход Angularified элементу, предполагая, что когда вы начинаете анимацию, значение изменится «очень скоро сейчас».

person David Boike    schedule 12.06.2014