如何在DOM完成渲染后运行一个指令?

115

我遇到了一个看似简单的问题,但是在阅读 Angular JS 文档后没有找到明显的解决方案。

我有一个 Angular JS 指令,它根据其他 DOM 元素的高度进行一些计算,以定义 DOM 中容器的高度。

指令内部类似于以下内容:

return function(scope, element, attrs) {
    $('.main').height( $('.site-header').height() -  $('.site-footer').height() );
}
问题在于指令运行时,$('site-header') 找不到,返回一个空数组,而不是我需要的 jQuery 封装的 DOM 元素。
是否有回调函数可以在我的指令中使用,只有在 DOM 加载完成后才运行,并且我可以通过正常的 jQuery 选择器样式查询访问其他 DOM 元素?

1
你可以使用 scope.$on() 和 scope.$emit() 来使用自定义事件。不确定这是否是正确/推荐的方法。 - Tosh
6个回答

137

这取决于你的$('site-header')是如何构建的。

你可以尝试使用0延迟的$timeout。例如:

return function(scope, element, attrs) {
    $timeout(function(){
        $('.main').height( $('.site-header').height() -  $('.site-footer').height() );
    });        
}

它的工作原理解释在这里:onetwo

不要忘记在指令中注入$timeout

.directive('sticky', function($timeout)

5
谢谢,我尝试让这个工作很久,直到我意识到我没有将$timeout传递到指令中。哎呀,现在一切都可以工作了,谢谢。 - Jannis
5
жҳҜзҡ„пјҢжӮЁйңҖиҰҒеғҸиҝҷж ·е°Ҷ $timeout дј йҖ’з»ҷжҢҮд»Өпјҡ .directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); }; - Vladimir Starkov
19
您提供的解释说明了在JavaScript中为什么超时技巧有效,但在AngularJS上下文中无效。根据官方文档:“_[...] 4.使用$ evalAsync队列来安排需要在当前堆栈帧之外但在浏览器视图渲染之前发生的工作。通常使用setTimeout(0)来完成此操作,但是setTimeout(0)方法速度较慢,并且可能会导致视图闪烁,因为浏览器在每个事件之后呈现视图。[...]_”(我强调) - Alberto
12
我遇到了类似的问题,并发现在执行指令之前需要大约300毫秒让DOM加载。我真的不喜欢这样插入看起来任意的数字。我相信DOM加载速度会因用户而异。那么,我如何确保300毫秒对使用我的应用程序的任何人都有效? - keepitreal
5
对这个答案不太满意,虽然它似乎回答了原帖的问题,但它非常特定于他们的情况,并且与更普遍的问题形式(即在DOM加载后运行指令)的相关性并不明显,而且只是太hacky了。 它没有具体涉及Angular方面的任何内容。 - abbood
显示剩余5条评论

44

这是我的做法:

app.directive('example', function() {

    return function(scope, element, attrs) {
        angular.element(document).ready(function() {
                //MANIPULATE THE DOM
        });
    };

});

1
不需要使用angular.element,因为元素已经在那里可用:element.ready(function(){ - timhc22
2
@timhc22元素是指触发指令的DOM元素的引用,您的建议不会导致对页面文档对象的DOM元素引用。 - tobius
这个代码不能正常工作。我通过这种方法得到的offsetWidth为0。 - Alexey Sh.

37

也许作者已不再需要我的答案,但为了完整起见,我认为其他用户可能会发现它有用。最好且最简单的解决方法是在返回函数的正文中使用 $(window).load()。(或者您可以使用 document.ready,这取决于您是否需要所有图像。)

在我看来,使用 $timeout 是一个非常弱的选择,可能会在某些情况下失败。

以下是我会使用的完整代码:

.directive('directiveExample', function(){
   return {
       restrict: 'A',
       link: function($scope, $elem, attrs){

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});

1
你能详细说明为什么它“在某些情况下可能会失败”吗?你指的是哪些情况? - rryter
6
你假设这里已经可用了 jQuery。 - Jonathan Cremin
3
@JonathanCremin 根据原帖,jQuery选择是问题所在。 - Nick Devereaux
1
这个很好用,但是如果有一个帖子使用指令来构建新的项目,那么窗口加载将不会在初始加载后触发,因此将无法正确地运行。 - Brian Scott
@BrianScott - 我使用了$(window).load的组合来进行初始页面渲染(我的用例是等待嵌入式字体文件),然后使用element.ready来处理视图切换。 - aaaaaa

8

有一个ngcontentloaded事件,我认为你可以使用它。

.directive('directiveExample', function(){
   return {
       restrict: 'A',
       link: function(scope, elem, attrs){

                $$window = $ $window


                init = function(){
                    contentHeight = elem.outerHeight()
                    //do the things
                }

                $$window.on('ngcontentloaded',init)

       }
   }
});

22
你能解释一下 $ $window 的作用是什么吗? - Catfish
2
看起来像是一些Coffeescript,也许本意是要注入$($window)和$window到指令中。 - mdob

5
如果由于外部资源原因无法使用 $timeout,并且由于定时问题而无法使用指令,那么请使用广播。在所需的外部资源或长时间运行的控制器/指令完成后添加 $scope.$broadcast("variable_name_here");。然后在外部资源加载完成后添加以下内容。
$scope.$on("variable_name_here", function(){ 
   // DOM manipulation here
   jQuery('selector').height(); 
}

例如,在延迟的HTTP请求的承诺中。
MyHttpService.then(function(data){
   $scope.MyHttpReturnedImage = data.image;
   $scope.$broadcast("imageLoaded");
});

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}

2
这并不能解决问题,因为加载的数据并不意味着它们已经在DOM中呈现,即使它们绑定到DOM元素的适当作用域变量中。在它们在作用域中加载和在DOM中呈现输出之间存在一段时间间隔。 - René Stalder

1

我曾遇到类似的问题,现在想在这里分享我的解决方案。

我有以下HTML代码:

<div data-my-directive>
  <div id='sub' ng-include='includedFile.htm'></div>
</div>

问题:在父div指令的link函数中,我想要使用jquery来操作子div#sub。但是由于ng-include尚未完成,因此它只给了我一个空对象。所以我首先使用$timeout做了一个不太好的解决方法,虽然有效,但延迟参数取决于客户端速度(没有人喜欢这样)。
有效但不太好的解决方法:
app.directive('myDirective', [function () {
    var directive = {};
    directive.link = function (scope, element, attrs) {
        $timeout(function() {
            //very dirty cause of client-depending varying delay time 
            $('#sub').css(/*whatever*/);
        }, 350);
    };
    return directive;
}]);

Here's the clean solution:

app.directive('myDirective', [function () {
    var directive = {};
    directive.link = function (scope, element, attrs) {
        scope.$on('$includeContentLoaded', function() {
            //just happens in the moment when ng-included finished
            $('#sub').css(/*whatever*/);
        };
    };
    return directive;
}]);

也许对某些人有帮助。

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