简体   繁体   English

$ timeout是避免AngularJS指令中jQuery插件渲染问题的唯一/推荐方法吗?

[英]Is $timeout the only/recommended way to avoid jQuery plugin rendering problems in AngularJS directives?

I'm porting a jQuery webapp to AngularJS (<- beginner!). 我正在将jQuery Webapp移植到AngularJS(<-初学者!)。

To integrate bxSlider along with some templating stuff, I wrote following directive: 为了集成bxSlider和一些模板化的东西,我编写了以下指令:

[Edit] better have a look at jsFiddle jsfiddle.net/Q5AcH/2/ [/Edit] . [编辑]最好看看jsFiddle jsfiddle.net/Q5AcH/2/ [/ Edit]

angular.module('myApp')
    .directive('docListWrapper', ['$timeout', function ($timeout) {
        return {
            restrict: 'C',
            templateUrl: 'partials/doc-list-wrapper.html',
            scope: { docs: '=docs'},
            link: function (scope, element, attrs) {

                $timeout(function () {
                    element
                        .children('.doc-list')
                        .not('.ng-hide')
                        .bxSlider(); // <-- jQuery plugin doing heavy DOM manipulation
                }, 100); // <-------------- timeout in millis
            }
        };
    }]);

Without $timeout there is the problem that bxSlider cannot calculate sizes of the freshly created elements or doesn't find them at all. 如果没有$timeout则存在一个问题,即bxSlider无法计算新创建的元素的大小或根本找不到它们。

I'm a bit concerned that using a long timeout-value might cause flickering while using a short value could cause problems on slow machines. 我有点担心,使用较长的超时值可能会导致闪烁,而使用较短的值可能会在慢速计算机上引起问题。

In my real application (of course with more data and more sections than in the jsFiddle) I observed something strange: 在我的实际应用程序中(当然比jsFiddle中包含更多的数据和更多的部分),我观察到了一些奇怪的东西:

When I play around with the timeout value, using 10 or more milliseconds is enough so the jQuery plugin bxSlider finds a complete DOM. 当我尝试使用超时值时,使用10毫秒或更多毫秒就足够了,因此jQuery插件bxSlider可以找到完整的DOM。 With less time waiting (9 millis or less), the plugin is not able to wrap the <ul> as it should. 等待时间更少(9毫秒或更短),该插件无法包装<ul>

But the problem of a very nasty flickering is still present. 但是,仍然存在非常讨厌的闪烁的问题。

In the fiddle, probably due to a smaller DOM, the flickering is not visible in Chrome + Firefox, only with Internet Explorer 10. 可能是由于DOM较小,在闪烁中,只有Internet Explorer 10才在Chrome + Firefox中看不到闪烁。

I don't want to rely on empiric values for $timeout which could be highly dependent on machine, os, rendering engine, angular version, blood preasure, ... 我不想依赖$timeout经验值,它可能高度依赖于机器,操作系统,渲染引擎,角度版本,血液压力,...

Is there a robust workaround? 有健壮的解决方法吗?

I've found some examples with event listeners ( $on , $emit ) and with some magic done with ng-repeat $scope.$last . 我发现了一些事件监听器示例( $on$emit )以及ng-repeat $scope.$last完成的一些魔术。 If I can remove flickering, I'd accept some coupling between components, even this does not fit nice with AngularJS' ambition. 如果我可以消除闪烁,那么我会接受组件之间的某种耦合,即使这与AngularJS的雄心也不相称。

Your problem is a racing condition problem, so you can't just remove the $timeout . 您的问题是赛车状况问题,因此您不能仅删除$timeout Pretty much what happens is: 几乎发生了什么事:

  1. Angular compiles both elements; Angular编译这两个元素;
  2. Angular links you bx-slider element; bx-slider元素的角度链接;
  3. bx-slider looks for <li> elements (none at this time) and create the list; bx-slider查找<li>元素(目前没有)并创建列表;
  4. Angular links the ng-repeat and build the <li> list and resolve the bindings. Angular链接ng-repeat并构建<li>列表并解析绑定。

So, to solve the first aspect of racing condition (build the component only after all <li> are ready), you should expose a update method at bxSlider directive and create a sub-directive that would call a update function in the bxSlider controller, using the $scope.$last trick : 因此,要解决竞争条件的第一个方面(仅在所有<li>准备就绪后才构建组件),应在bxSlider指令中公开一个update方法,并创建一个子指令,该子指令将在bxSlider控制器中调用update函数,使用$scope.$last技巧

.directive('bxSlider', function () {
    var BX_SLIDER_OPTIONS = {
        minSlides: 2,
        maxSlides: 7,
        slideWidth: 120
    };

    return {
        restrict: 'A',
        require: 'bxSlider',
        priority: 0,
        controller: function() {},
        link: function (scope, element, attrs, ctrl) {
            var slider;
            ctrl.update = function() {
                slider && slider.destroySlider();
                slider = element.bxSlider(BX_SLIDER_OPTIONS);
            };
        }
    }
}])
.directive('bxSliderItem', function($timeout) {
    return {
        require: '^bxSlider',
        link: function(scope, elm, attr, bxSliderCtrl) {
            if (scope.$last) {
                bxSliderCtrl.update();
            }
        }
    }
})

This solution would even give you the ability to add new itens to the model, for everytime you have a new $last item, the bxSlider would be built. 该解决方案甚至使您能够向模型添加新的iten,对于每当您有新的$last项时,都会构建bxSlider。 But again, you would run into another racing condition. 但是同样,您会遇到另一种赛车状况。 During step 3 , the slider component duplicates the last element, in order to show it just before the first, to create a 'continuity' impression (take a look at the fiddle to understand what I mean). 第3步中 ,滑块组件复制最后一个元素,以便在第一个元素之前显示它,以创建“连续性”印象(看小提琴以了解我的意思)。 So now your flow is like: 所以现在您的流程就像:

  1. Angular compiles both elements; Angular编译这两个元素;
  2. Angular links you bx-slider element; bx-slider元素的角度链接;
  3. Angular links the ng-repeat and build the <li> list; Angular链接ng-repeat并建立<li>列表;
  4. Your code calls the parent update function, that invokes your component building process, that duplicates the last element; 您的代码将调用父update函数,该函数将调用您的组件构建过程,该过程将复制最后一个元素。
  5. Angular resolves the bindings. Angular解析绑定。

So now, your problem is that the duplications made by the slider, carries only the templates of the elements, as Angular hadn't yet resolved it bindings. 所以现在,您的问题是滑块所进行的复制仅包含元素的模板,因为Angular尚未解决其绑定问题。 So, whenever you loop the list, you gonna see a broken content. 因此,每当循环列表时,您都会看到损坏的内容。 To solve it, simply adding a $timeout of 1 millisecond is enough, because you gonna swap the order of steps 4 and 5, as Angular binding resolution happens in the same stack as the $digest cycle, so you should have no problem with it: 要解决它,只需添加 1毫秒$timeout就足够了,因为您将交换第4步和第5步的顺序,因为Angular绑定解析与$digest循环位于同一堆栈中,所以您应该没有问题:

.directive('bxSliderItem', function($timeout) {
    return {
        require: '^bxSlider',
        link: function(scope, elm, attr, bxSliderCtrl) {
            if (scope.$last) {
                $timeout(bxSliderCtrl.update, 1);
            }
        }
    }
})

But you have a new problem with that, as the Slider duplicates the boundaries elements, these duplications are not overviewed by AngularJs digest cycle, so you lost the capability of model binding inside these components. 但是您有一个新问题,因为Slider复制边界元素,AngularJ的摘要循环不会概述这些重复,因此您失去了在这些组件内部进行模型绑定的功能。

After all of this, what I suggest you is to use a already-adapted-angularjs-only slide solution . 在完成所有这些之后,我建议您使用仅已适应angularjs的幻灯片解决方案

So, summarizing: 因此,总结一下:

  1. you can use 1 millisecond delay in your solution, because Angular digest cycle is synchronous; 您可以在解决方案中使用1毫秒的延迟,因为Angular摘要周期是同步的; - but you lost the capability to add new items to your list - 但是您失去了将新项目添加到列表中的功能
  2. you can use a $scope.$last trick with the $timeout as well - but you lost Angular bindings and if this components have any instance (selected, hover), you gonna have problem 您也可以将$scope.$last技巧与$timeout一起使用- 但您丢失了Angular绑定,并且如果此组件具有任何实例(已选择,悬停),则可能会遇到问题
  3. you could use an already written solution (like the one I suggested). 您可以使用已经写好的解决方案(例如我建议的解决方案)。
  4. you could write your own AngularJs native solution. 您可以编写自己的AngularJs本机解决方案。

My answer seems round-about but might remove your need for $timeout. 我的回答似乎是回旋,但可能会消除您对$ timeout的需求。 Try making another directive and attaching it to the li element. 尝试制作另一个指令并将其附加到li元素。 Something like the following pseudo code: 类似于以下伪代码:

angular.module('myApp').directive('pdfClick', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            $element.bxSlider().delegate('a', 'click', pdfClicked);
        }
    }
});

<li class="doc-thumbnail" ng-repeat="doc in docs" pdfClick>

It should attach the click event to every list item's anchor generated by ng repeat. 它应该将click事件附加到ng repeat生成的每个列表项的锚点上。

Data hasn't yet arrived at scope at rendering time! 数据在渲染时尚未到达范围!

It turned out the problem was that the data has not been present at the time the directive was executed (linked). 原来的问题是在执行(链接)指令时数据不存在。

In the fiddle, data was accessible in the scope very fast. 在小提琴中,范围内的数据访问非常快。 In my application it took more time since it was loaded via $http . 在我的应用程序中,由于它是通过$http加载的,因此花费了更多时间。 This is the reason why a $timeout of < 10ms was not enough in most cases. 这就是为什么在大多数情况下$timeout <10ms不足的原因。

So the solution in my case was, instead of 所以我的解决方案是

angular.module('myApp')
    .directive('docListWrapper', ['$timeout', function ($timeout) {
        return {
            restrict: 'C',
            templateUrl: 'partials/doc-list-wrapper.html',
            scope: { docs: '=docs'},
            link: function (scope, element, attrs) {

                $timeout(function () { // <-------------------- $timeout
                    element
                        .children('.doc-list')
                        .not('.ng-hide')
                        .bxSlider();
                }, 10);
            }
        };
    }]);

I now have this: 我现在有这个:

angular.module('myApp')
    .directive('docListWrapper', [function () {
        return {
            restrict: 'C',
            templateUrl: 'partials/doc-list-wrapper.html',
            scope: { docs: '=docs'},
            link: function (scope, element, attrs) {

                scope.$watch('docs', function () { // <---------- $watch
                    element
                        .children('.doc-list')
                        .not('.ng-hide')
                        .bxSlider();
                });
            }
        };
    }]);

Maybe there is a more elegant solution for this problem, but for now I'm happy that it works. 也许对于这个问题有一个更优雅的解决方案,但是现在我很高兴它能起作用。

I hope this helps other AngularJS beginners. 我希望这对其他AngularJS初学者有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM