AngularJS Digest Cycle иногда не может обновить область

Angular Devs!

Я сталкиваюсь с небольшой проблемой при совместном использовании ngAnimate и UI-Router. По сути, у меня есть пять дочерних состояний ("слайдов"), которые составляют мое основное содержание, а также кнопки на каждой стороне. Эти кнопки переходят на предыдущий и следующий слайды соответственно при нажатии.

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

Направление, в которое мои слайды входят и выходят из области просмотра (имейте в виду, что слайды являются состояниями UI-Router, поэтому они удаляются из DOM в конце анимации), определяется классом, который я применяю к родительскому элементу. Применение "левого" класса делает слайды анимированными влево, и наоборот для "правого" класса.

Код разбит на несколько элементов:

  • appSlideDirective
    • директива, содержащая слайд-кнопки и встроенный div вида UI-Router (контент)
    • обрабатывает нажатия кнопок пользователя и передает события click в baseController для переходов состояний
  • baseController
    • контроллер, который принадлежит родительскому модулю, управляет stateService
  • stateService (используется только baseController)
    • перемещается между состояниями UI-Router, когда директива отправляет ему события
  • slideService (используется только директивой)
    • управляет добавлением и удалением классов 'left' и 'right' (с помощью ng-click)

Директива HTML:

<div id="slide-container">
    <div id="slide" data-ng-class="{ left: slide.getTransitionState().left, right: slide.getTransitionState().right }">
        <div id="slide-content" data-ui-view></div>
    </div>
</div>

Соответствующий код директивы:

angular.module('app').directive('appSlide', function(slideService) {
restrict: 'A',
    scope: {
        currentState: '=',
        numberOfStates: '=',
        loadPreviousState: '&onPreviousState',
        loadNextState: '&onNextState'
    },
    templateUrl: 'slide.html',
    link: function(scope, element) {
        scope.$on('$destroy', function() {
            element.empty();
        });
    },
    controller: function($scope, $timeout) {
        var vm = this;

        // ...

        vm.loadPreviousSlide = function() {
            slideService.preparePreviousSlide();
            $scope.loadPreviousState();
        });
        vm.loadNextSlide = function() {
            slideService.prepareNextSlide();
            $scope.loadNextState();
        });
        vm.getTransitionState = function() {
            return slideService.getTransitionState();
        };

        // ...

    },
    controllerAs: 'slide'
});

Соответствующий код контроллера:

angular.module('app').controller('baseController', function(stateService) {
    var vm = this;

    // ...

    vm.loadPreviousState = function() {
        stateService.loadPreviousState();
    };
    vm.loadNextState = function() {
        stateService.loadNextState();
    };

    // ...
});

Соответствующий сервисный код:

angular.module('app').factory('slideService', function() {
    var _transitionState = {
        left: false,
        right: false
    };

    return {
        preparePreviousSlide: function() {
            _transitionState.right = false;
            _transitionState.left = true;
        },
        prepareNextSlide: function() {
            _transitionState.left = false;
            _transitionState.right = true;
        },
        getTransitionState: function() {
            return _transitionState;
        }

        // ...
    };
});

angular.module('app').factory('stateService', function($state) {

    // ...

    return {

        // ...

        loadPreviousState: function() {
            var targetState = _calculatePreviousState();
            $state.go( targetState );
        },
        loadNextState: function() {
            var targetState = _calculateNextState();
            $state.go( targetState );
        }

        // ...
    };
});

Это работает довольно хорошо - по большей части. При переходе на первый слайд анимация при загрузке страницы не выполняется, поскольку отсутствуют ни "левый", ни "правый" класс. Точно так же, переключение направлений (т. Е. Движение влево дважды, затем вправо один раз) приводит к тому, что слайды появляются с неправильной стороны, поскольку класс 'left'/'right' предыдущей анимации все еще находится на элементе.

После некоторого копания, я думаю, что наткнулся на причину сбоя первой / противоположной анимации: событие ng-click запускает цикл дайджеста, который блокирует любые изменения области действия до тех пор, пока все его действия не будут завершены, то есть состояние изменен до того, как будет создан правильный класс 'left'/'right' и соответствующие стили CSS3 могут быть применены. Итак, мне нужно запустить еще один цикл дайджеста, как только я правильно обновлю "левый" и "правый" классы, и только после этого изменю состояние.

Я прошел много итераций кода, и моя текущая реализация опирается на $scope.$ Apply и сервис $timeout. Чтобы запустить другой цикл дайджеста, я запускаю $scope.$ Apply - и использую $timeout для предотвращения ошибок "в процессе". Затем я запускаю изменения своего состояния, когда обещание $timeout разрешено.

Обновления относительной директивы

vm.loadPreviousSlide = function() {
    var promise = $timeout(function() {
        $scope.$apply(function() {
            slideService.preparePreviousSlide();
        });
    }, 0, false);
    promise.then(function() {
        $scope.loadPreviousState();
    });
};
vm.loadNextSlide = function() {
    var promise = $timeout(function() {
        $scope.$apply(function() {
            slideService.prepareNextSlide();
        });
    }, 0, false);
    promise.then(function() {
        $scope.loadNextState();
    });
};

У меня есть две проблемы с этим. Во-первых, это оскорбляет мою чувствительность - кажется мне более сложным, чем нужно. Во-вторых, это не работает в 100% случаев. Иногда это работает, а иногда нет. Я понятия не имею, почему это не удается.

Что, вы парни, думаете? Дайте мне знать, если вы придумали какие-нибудь лучшие решения, или если вы можете выяснить, почему моя иногда не удается! Спасибо за ваше время.

О, еще одна вещь - я довольно плохо знаком с AngularJS, поэтому, пожалуйста, не стесняйтесь давать рекомендации и по структуре кода или по методологии!

1 ответ

Решение

Я решил эту проблему пару месяцев назад.

Я был вынужден реализовать решение, которое казалось бы менее "угловатым", но работало 100% времени.

Короче говоря, я закончил добавлять и удалять классы вручную, а не ждать следующего цикла дайджеста.

Например:

var slide = angular.element(document.querySelector('#slide'));
slide.removeClass('right');
slide.addClass('left');

Тогда было тривиально запускать соответствующие переходы состояний.

Другие вопросы по тегам