AngularJS的脏检查循环偶尔无法更新作用域

4

Angular开发者们!

当我同时使用ngAnimate和UI-Router时,遇到了一些问题。基本上,我的主要内容由五个子状态(“幻灯片”)和每一侧的一个按钮组成。点击这些按钮时,它们分别转到前一张和后一张幻灯片。

我使用CSS3转换来动画化幻灯片的进入和退出。当用户点击左边的按钮时,当前幻灯片向右退出,而“前一个”幻灯片从左侧进入。右侧按钮被单击时,情况相反:当前幻灯片向左退出,而“下一个”幻灯片从右侧进入。这些幻灯片循环播放,因此幻灯片零转到幻灯片四,反之亦然。

我的幻灯片进入和退出视口的方向(请记住,这些幻灯片是UI-Router状态,因此它们在动画结束时会从DOM中删除)由我应用于父元素的类决定。应用“left”类使幻灯片向左动画,反之亦然。

代码分为几个元素:

  • appSlideDirective
    • 指令,包含幻灯片按钮和一个嵌入式的UI-Router视图div(内容)
    • 处理用户按钮按下事件并将点击事件传递给baseController进行状态转换
  • baseController
    • 属于父模块的控制器,管理stateService
  • stateService(仅由baseController使用)
    • 在指令发送事件时在UI-Router状态之间导航
  • slideService(仅由指令使用)
    • 通过ng-click管理'left'和'right'类的添加和删除

指令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' 类存在,第一次幻灯片转换失败。同样地,切换方向(即向左两次,然后向右一次)会使幻灯片从错误的方向出现,因为上一个动画的 'left'/'right' 类仍然存在于元素上。
经过一些探索,我认为我已经发现了为什么第一次/相反的动画失败的原因:ng-click 事件开始了一个 digest 循环,阻止任何作用域更改发生,直到它的所有操作完成--这意味着状态在正确的 'left'/'right' 类被更新并且对应的 CSS3 变换样式可以被应用之前就已经被更改了。因此,在我正确更新了 'left' 和 'right' 类并且只有在另一个 digest 循环被运行之后才能更改状态。
我经历了许多代码迭代,我的当前实现依赖于 $scope.$apply 和 $timeout 服务。要强制进行另一个 digest 循环,我运行 $scope.$apply--我使用 $timeout 来防止 "in progress" 错误。然后当 $timeout promise 被解决时,我运行我的状态更改。
相对指令更新
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相当新,请随时提供关于代码结构或方法的建议!

最初的ng-click事件会触发一个digest循环,但此时'left'/'right'类还没有准备好 - 因此当它们准备好时,我调用$scope.$apply()。不幸的是,初始的digest循环将阻止$scope.$apply()启动新的循环,因此我需要在回调/承诺中实现它。根据文档,延迟为0的$timeout将在当前digest循环结束后立即运行。此外,我禁用了$timeout应用程序包装(第三个参数为false),因为否则我无法使其工作。 - SamuelMS
1
尝试使用以下代码替换原有的代码:var promise = $scope.$evalAsync(function() { slideService.prepareNextSlide(); }); - Pankaj Parkar
$evalAsync 不会返回 promise,所以我不得不保留 $timeout。我只是用 $scope.$evalAsync 替换了 $scope.$apply。看起来它100% 工作 - 我会在更多测试后再发布。我不禁想知道是否有其他解决方案,不依赖于 $timeout(禁用 $apply 包装)和 $evalAsync。 - SamuelMS
我需要添加答案吗? - Pankaj Parkar
1
@pankajparkar 让我再进行一些测试。如果它是100%功能正常的,我会告诉你的。 - SamuelMS
显示剩余4条评论
1个回答

0

我几个月前解决了这个问题。

我被迫实施了一个明显不太"Angular"的解决方案,但是它百分之百有效。

长话短说,最后我选择手动添加和删除类,而不是等待下一次摘要周期。

举个例子:

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

这时候,触发适当的状态转换就变得微不足道了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接