Angular JS指令有后期渲染回调吗?


139

我刚刚得到了我的指令,可以插入一个模板以附加到其元素,如下所示:

# CoffeeScript
.directive 'dashboardTable', ->
  controller: lineItemIndexCtrl
  templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
  (scope, element, attrs) ->
    element.parent('table#line_items').dataTable()
    console.log 'Just to make sure this is run'

# HTML
<table id="line_items">
    <tbody dashboard-table>
    </tbody>
</table>

我还使用了一个名为DataTables的jQuery插件。它的一般用法是这样的:$('table#some_id')。dataTable()。您可以将JSON数据传递到dataTable()调用中以提供表数据,或者您可以将数据存储在页面上,其余的将由数据完成。 。

但是问题是我必须在准备好DOM之后在table#line_items上调用dataTable()。我上面的指令在将模板附加到指令的元素之前,先调用dataTable()方法。有没有一种方法可以在追加之后调用函数?

谢谢您的帮助!

安迪回答后的更新1:

我想确保链接方法仅在页面上的所有内容之后都被调用,因此我更改了指令进行了一些测试:

# CoffeeScript
#angular.module(...)
.directive 'dashboardTable', ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        element.find('#sayboo').html('boo')

      controller: lineItemIndexCtrl
      template: "<div id='sayboo'></div>"

    }

而且我确实在div#sayboo中看到了“ boo”。

然后我尝试我的jQuery数据表调用

.directive 'dashboardTable',  ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        element.parent('table').dataTable() # NEW LINE

      controller: lineItemIndexCtrl
      templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
    }

那里没有运气

然后我尝试添加超时:

.directive 'dashboardTable', ($timeout) ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        $timeout -> # NEW LINE
          element.parent('table').dataTable()
        ,5000
      controller: lineItemIndexCtrl
      templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
    }

那行得通。因此,我想知道非计时器版本的代码出了什么问题?


1
@adardesign不,我从没做过,我不得不使用计时器。由于某种原因,回调实际上不是这里的回调。我有一个包含11列和100的行的表,因此自然的角度看起来像是用于数据绑定的好选择;但我还需要使用jquery Datatables插件,该插件就像$('table')。datatable()一样简单。使用指令或仅具有所有行的愚蠢json对象并使用ng-repeat进行迭代,我无法让我的$()。datatable()在呈现表格html元素之后运行,所以我目前的诀窍是定时器检查$('tr')。length是否大于3(页眉/页脚的b / c)
Nik So

2
@adardesign是的,我尝试了所有编译方法,编译方法返回了一个包含postLink / preLink方法的对象,编译方法仅返回了一个函数(即链接函数),链接方法(没有编译方法,因为据我所知,如果您有一个返回链接方法的编译方法,则将忽略链接函数。)没有任何作用,因此必须依靠良好的旧$ timeout。如果我发现有更好的工作,或者只是发现回调确实像回调一样工作,将更新此帖子
Nik So

Answers:


215

如果未提供第二个参数“ delay”,则默认行为是在DOM完成渲染之后执行该功能。因此,请使用$ timeout而不是setTimeout:

$timeout(function () {
    //DOM has finished rendering
});

8
为什么在文档中没有解释?
盖伊2014年

23
没错,我的回答有点误导,因为我试图使其变得简单。完整的答案是,这种效果不是Angular的结果,而是浏览器的结果。$timeout(fn)最终调用setTimeout(fn, 0)会中断Javascript的执行,并允许浏览器先呈现内容,然后再继续执行Javascript。
议会

7
可以将浏览器视为将某些任务(例如“ javascript执行”和“ DOM渲染”)分别放在队列中,渲染后将当前运行的“ javascript执行”推到队列的后面是什么setTimeout(fn,0) 。
议会

2
@GabLeRoux是的,除了$ timeout具有在运行后调用$ scope。$ apply()的额外好处外,其效果相同。如果myFunction更改了作用域中的变量,则需要使用_.defer()手动调用它。
议会

2
我遇到这样的情况,这对在page1上的ng-repeat渲染一堆元素无济于事,然后我转到page2,然后又返回到page1,并尝试使ng-repeat元素的父级较高。它返回错误的高度。如果我将超时时间设为1000毫秒,则可以正常工作。
yodalr

14

我遇到了同样的问题,我相信答案确实是不。请参阅Miško的评论小组中的一些讨论

Angular可以跟踪它为操纵DOM而进行的所有函数调用均已完成,但是由于这些函数可能会触发异步逻辑,这些逻辑在返回后仍会更新DOM,因此我们不希望Angular知道它。任何回调角度给出了可能有时会工作,但不会是安全的依靠。

与您一样,我们使用setTimeout启发式解决了这一问题。

(请记住,并非所有人都同意我的意见-您应该阅读上面链接上的评论,然后看看您的想法。)


7

您可以使用“链接”功能(也称为postLink),该功能在放入模板后运行。

app.directive('myDirective', function() {
  return {
    link: function(scope, elm, attrs) { /*I run after template is put in */ },
    template: '<b>Hello</b>'
  }
});

如果您打算制定指令,请阅读此书,这对您有很大帮助:http : //docs.angularjs.org/guide/directive


嗨,安迪,非常感谢您的回答;我确实尝试过链接功能,但是我不介意在您编写代码时再次尝试它。我花了最后1.5天的时间在该说明页上进行阅读;并查看angular网站上的示例。现在将尝试您的代码。
Nik So So

啊,我知道您现在正在尝试建立链接,但您做错了。如果只返回一个函数,则假定它是链接。如果返回对象,则必须使用键“ link”将其返回。您还可以从编译函数返回链接函数。
Andrew Joslin 2012年

嗨,安迪,我的成绩回来了;我几乎失去了理智,因为我基本上确实做了您在这里的回答。请参阅我的更新
聂所以

嗯,尝试如下操作:<table id =“ bob”> <tbodydashboard-table =“#bob”> </ tbody> </ table>然后在您的链接中,执行$(attrs.dashboardTable).dataTable()以确保选择正确。或者,我猜您已经尝试过了。。我真的不确定链接是否无效。
Andrew Joslin 2012年

这对我
有用

7

尽管我的答案与数据表无关,但它解决了DOM操作和例如针对以异步方式更新其内容的元素上使用的指令的jQuery插件初始化的问题。

除了实现超时之外,您还可以添加一只手表来监听内容更改(甚至是其他外部触发器)。

在我的情况下,一旦ng-repeat创建了我的内部DOM,我就使用了这种变通方法来初始化jQuery插件;在另一种情况下,在控制器的scope属性更改之后,我仅使用它来操作DOM。这是我做的...

HTML:

<div my-directive my-directive-watch="!!myContent">{{myContent}}</div>

JS:

app.directive('myDirective', [ function(){
    return {
        restrict : 'A',
        scope : {
            myDirectiveWatch : '='
        },
        compile : function(){
            return {
                post : function(scope, element, attributes){

                    scope.$watch('myDirectiveWatch', function(newVal, oldVal){
                        if (newVal !== oldVal) {
                            // Do stuff ...
                        }
                    });

                }
            }
        }
    }
}]);

注意:不仅可以将myContent变量强制转换为my-directive-watch属性的布尔值,还可以想象在那里存在任意表达式。

注意:像上面的示例中那样,隔离范围只能对每个元素执行一次-尝试对同一元素使用多个指令将导致$ compile:multidir错误-请参阅:https : //docs.angularjs.org /错误/ $ compile / multidir


7

回答这个问题可能为时已晚。但是仍然有人可以从我的答案中受益。

我有类似的问题,在我的情况下,我不能更改指令,因为它是一个库,而更改该库的代码不是一个好习惯。所以我所做的就是使用变量来等待页面加载,并在html中使用ng-if等待呈现特定元素。

在我的控制器中:

$scope.render=false;

//this will fire after load the the page

angular.element(document).ready(function() {
    $scope.render=true;
});

在我的html中(在我的情况下html组件是一个画布)

<canvas ng-if="render"> </canvas>

3

我有同样的问题,但是使用Angular + DataTable和fnDrawCallback+ 行分组 + $ compiled嵌套指令。我将$ timeout放入了fnDrawCallback函数中以修复分页渲染。

在示例之前,基于row_grouping来源:

var myDrawCallback = function myDrawCallbackFn(oSettings){
  var nTrs = $('table#result>tbody>tr');
  for(var i=0; i<nTrs.length; i++){
     //1. group rows per row_grouping example
     //2. $compile html templates to hook datatable into Angular lifecycle
  }
}

后例:

var myDrawCallback = function myDrawCallbackFn(oSettings){
  var nTrs = $('table#result>tbody>tr');
  $timeout(function requiredRenderTimeoutDelay(){
    for(var i=0; i<nTrs.length; i++){
       //1. group rows per row_grouping example
       //2. $compile html templates to hook datatable into Angular lifecycle
    }
  ,50); //end $timeout
}

即使是短暂的超时延迟也足以使Angular呈现我的编译后的Angular指令。


只是好奇,您是否有一个很大的表且包含许多列?因为我确实发现我需要一个很烦人的毫秒(> 100),所以不要让dataTable()叫停
Nik So

我发现问题发生在DataTable页面导航中,结果集从2行到超过150行。所以,不,我认为表的大小不是问题,但是DataTable可能添加了足够的渲染开销以减少几毫秒的时间。我的重点是通过最少的AngularJS集成使行分组在DataTable中工作。
JJ Zabkar


0

这是在浅渲染后对动作进行编程的指令。由浅我的意思是呈现非常元素后,将评估,这将是无关的,当它的内容得到呈现。因此,如果需要一些子元素来执行后期渲染操作,则应考虑在此处使用它:

define(['angular'], function (angular) {
  'use strict';
  return angular.module('app.common.after-render', [])
    .directive('afterRender', [ '$timeout', function($timeout) {
    var def = {
        restrict : 'A', 
        terminal : true,
        transclude : false,
        link : function(scope, element, attrs) {
            if (attrs) { scope.$eval(attrs.afterRender) }
            scope.$emit('onAfterRender')
        }
    };
    return def;
    }]);
});

那么您可以执行以下操作:

<div after-render></div>

或任何有用的表达式,例如:

<div after-render="$emit='onAfterThisConcreteThingRendered'"></div>


呈现内容后,情况并非如此。如果此时我在元素<div after-render> {{blah}} </ div>中有一个表达式,则该表达式尚未求值。div的内容仍位于链接函数内{{blah}}。因此,从技术上讲,您是在呈现内容之前触发事件。
爱德华·奥拉米桑

这是渲染动作之后的一个浅水区,我从未声称过它很深
Sebastian Sastre

0

我使用以下指令进行此操作:

app.directive('datatableSetup', function () {
    return { link: function (scope, elm, attrs) { elm.dataTable(); } }
});

在HTML中:

<table class="table table-hover dataTable dataTable-columnfilter " datatable-setup="">

如果上述方法对您不起作用,请排除故障。

1)请注意,“ datatableSetup”等效于“ datatable-setup”。Angular将格式更改为驼峰式大小写。

2)确保在指令之前定义了app。例如简单的应用程序定义和指令。

var app = angular.module('app', []);
app.directive('datatableSetup', function () {
    return { link: function (scope, elm, attrs) { elm.dataTable(); } }
});

0

遵循无法预期的装载顺序这一事实,可以使用一种简单的解决方案。

让我们看一下指令与“指令用户”的关系。通常,指令的用户会向指令提供一些数据,或者使用指令提供的某些功能(函数)。另一方面,该指令希望在其范围内定义一些变量。

如果我们可以确保所有参与者在尝试执行这些动作之前都已满足其所有动作要求,那么一切应该都很好。

现在,该指令:

app.directive('aDirective', function () {
    return {
        scope: {
            input: '=',
            control: '='
        },
        link: function (scope, element) {
            function functionThatNeedsInput(){
                //use scope.input here
            }
            if ( scope.input){ //We already have input 
                functionThatNeedsInput();
            } else {
                scope.control.init = functionThatNeedsInput;
            }
          }

        };
})

现在是指令html的用户

<a-directive control="control" input="input"></a-directive>

在使用该指令的组件控制器中:

$scope.control = {};
...
$scope.input = 'some data could be async';
if ( $scope.control.functionThatNeedsInput){
    $scope.control.functionThatNeedsInput();
}

就是这样 有很多开销,但是您可能会丢失$ timeout。我们还假定使用该指令的组件在该指令之前被实例化,因为我们依赖于在实例化指令时存在的控制变量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.