Angular не обновляет ng-класс на ng-view

Я использую angular 1.6.5 для своего приложения angular и столкнулся с очень странным поведением.

Я хочу добиться следующего: при изменении ngroute я должен удалить активный класс из текущего представления, дождаться завершения анимации выхода, а затем добавить активный класс в новое представление.

Я настроил приложение и роуты в конфиге.

var app = angular.module('app', ['ngAnimate', 'ngRoute']);

app.config(function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl:"home.html",
        reloadOnSearch:false
      })
      .when('/about-us', {
        templateUrl:"about.html",
        reloadOnSearch:false
      })
      .when('/contact', {
        templateUrl:"contact.html",
        reloadOnSearch:false
      })
      .otherwise({
          template : "<h1>None</h1><p>Nothing has been selected</p>"
      });
});

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

app.service('animationProperties', function () {
  this.animationTimings = {
    views: 1000
  };
  this.visibility = {
    view : false
  };
});

У меня есть один главный контроллер с простой функцией отладки и одна функция onRouteChangeStart, которая должна удалить активный класс из текущего представления (сделав видимость представления логическим значением false):

app.controller('MainCtrl', function ($scope, $window, $location, 
                                     animationProperties) {

  $scope.animationProperties = animationProperties;

  $scope.$on('$routeChangeStart',function () {
    animationProperties.visibility.view = false;
  });

  $scope.toggleActive = function(){
    $scope.animationProperties.visibility.view = !$scope.animationProperties.visibility.view;
  }
});

И последнее, ngAnimate ожидает завершения анимации выхода, затем удаляет текущее представление (с помощью метода done ()) и снова входит в новое представление, делая логическое значение видимости истинным:

app.animation('.view', function($timeout, animationProperties) {
  return {
    enter: function(element, done) {
      $timeout(function () {
        animationProperties.visibility.view = true;
        $timeout(function () {
          done();
        }, animationProperties.animationTimings.views);//Wait to enter
      },animationProperties.animationTimings.views); //Wait for leave function
    },
    leave: function(element, done) {
      $timeout(function () {
        done();
      }, animationProperties.animationTimings.views);
    }
  }
});

Вот плункер.

При первом переключении страниц (из навигации) вы увидите, что все работает нормально, но при переходе на страницы второй раз класс просмотра не обновляется, поэтому анимация не воспроизводится. Во время отладки вы можете ясно видеть, что логическое значение видимости обновляется правильно, но ng-class при выходе из представления не обновляется.

Ваша помощь будет очень признательна !!!


person gogachinchaladze    schedule 22.09.2017    source источник


Ответы (1)


Цитируя себя здесь:

Вот что происходит:

  1. На $routeChangeStart вы изменяете значение, которое (после оценки) сообщит ngClass об удалении класса active из покидающего представления.
  2. В то же время $route начинает подготовку входящего представления, включая получение его шаблона.
  3. Как только все будет готово, оно запускает событие $routeChangeSuccess, которое сигнализирует ngView о начале смены двух представлений.
  4. Во время процесса замены ngView уничтожает область видимости покидающего представления, после чего наблюдатели области перестают быть ... наблюдаемыми.

Итак, если шаги 1–4 выполняются достаточно быстро, область покидающего представления уничтожается до того, как будут вычислены необходимые выражения для ngClass, чтобы удалить класс active. При первом посещении маршрута анимация работает, потому что $route должен сделать запрос сервера для входящего шаблона представления (что дает ngClass время для выполнения своей работы). Однако, когда вы посещаете ранее посещенный маршрут, шаблон уже кэшируется, и переход выполняется быстро.


Вы можете обойти это, намеренно замедляя получение шаблона (даже поворота виртуальной машины достаточно). Например:

app.decorator('$templateRequest', ($delegate, $timeout) => {
  const $templateRequest = (...args) => $delegate(...args).
    then(tmpl => $timeout().then(() => tmpl));
  Object.defineProperty($templateRequest, 'totalPendingRequests', {
    get: () => $delegate.totalPendingRequests,
    set: nv => $delegate.totalPendingRequests = nv,
  });
  return $templateRequest;
});

(Обновленный plnkr 1)

Другой способ обойти это - реализовать вашу собственную упрощенную синхронную версию ngClass, чтобы классы применялись немедленно, до того, как область видимости покидающего представления будет уничтожена. Грубая, неоптимизированная, не готовая к производству версия такой директивы может выглядеть так:

app.directive('myClass', () => (scope, elem, attrs) => {
  scope.$watchCollection(attrs.myClass, newValue => {
    Object.keys(newValue).forEach(c => {
      if (newValue[c]) {
        elem.addClass(c);
      } else {
        elem.removeClass(c);
      }
    });
  });
});

(обновленный plnkr 2)


Все это, как говорится, кажется странным:

  • Слушайте событие ($routeChangeStart).
  • Установите флаг (animationProperties.visibility.views), запускающий ngClass удаление класса.
  • При удалении класса запускается CSS-анимация.
  • А пока сделайте так, чтобы пользовательская анимация JavaScript (animation('.view')) синхронизировалась с анимацией CSS, а затем снова установите флаг.
  • Установив флаг обратно, активируйте противоположную анимацию CSS для входящего представления.

????

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

Или вы можете гораздо проще справиться с постепенным проявлением / исчезновением с помощью чистой CSS-анимации на основе автоматически добавленных / удаленных классов _19 _ / _ 20_ (это всего лишь 4 крошечных правила CSS: смайлик :):

[ng-view].ng-enter {
  /* Wait for the leaving view to...leave, then start transitioning in. */
  transition: opacity 1s ease 1s;
}

[ng-view].ng-leave {
  /* Start transitioning out. */
  transition: opacity 1s ease;
}

[ng-view].ng-enter,
[ng-view].ng-leave.ng-leave-active {
  /*
   * At the beginning of the entering animation and
   * at the end of the leaving animation,
   * the view must be fully invisible.
   */
  opacity: 0;
}

[ng-view].ng-enter.ng-enter-active,
[ng-view].ng-leave {
  /*
   * At the end of the entering animation and
   * at the beginning of the leaving animation,
   * the view must be fully visible.
   */
  opacity: 1;
}

(обновленный PLNKR 3)

person gkalpak    schedule 23.09.2017