Как мога да създам директива 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, след което задайте вътрешния HTML на оригиналния елемент на „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