是否有Angular JS指令的后期渲染回调?

时间:2012-06-20 17:37:28

标签: angularjs

我刚刚得到了我的指令来引入一个模板来附加到它的元素:

# 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()调用以提供表数据,或者您可以将数据放在页面上,然后它将完成其余的工作。我正在执行后者,将行放在HTML页面上

但问题是我必须在DOM准备就绪之后调用表#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') %>"
    }

这很有效。所以我想知道代码的非计时器版本出了什么问题?

10 个答案:

答案 0 :(得分:208)

如果是第二个参数,&#34;延迟&#34;没有提供,默认行为是在DOM完成渲染后执行该函数。因此,使用$ timeout:

而不是setTimeout
$timeout(function () {
    //DOM has finished rendering
});

答案 1 :(得分:14)

我有同样的问题,我相信答案真的不是。请参阅Miško's comment和一些discussion in the group

Angular可以跟踪它操作DOM所做的所有函数调用是否完整,但由于这些函数可能会触发在返回后仍然更新DOM的异步逻辑,因此无法预期Angular会知道它。任何回调Angular有时会可能工作,但依赖它是不安全的。

我们用setTimeout启发式地解决了这个问题。正如你所做的那样。

(请记住,并非所有人都同意我的观点 - 您应该阅读上述链接中的评论,看看您的想法。)

答案 2 :(得分: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

答案 3 :(得分:7)

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

不是实现超时,而是可以添加一个监听内容更改的监视(甚至是其他外部触发器)。

在我的情况下,我使用此解决方法初始化一个jQuery插件,一旦完成ng-repeat创建了我的内部DOM - 在另一种情况下,我使用它来在控制器上更改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 ...
                        }
                    });

                }
            }
        }
    }
}]);

注意:除了在my-directive-watch属性中将myContent变量强制转换为bool,我可以想象那里有任意表达式。

注意:隔离范围,如上例所示,每个元素只能执行一次 - 尝试在同一元素上使用多个指令执行此操作将导致$ compile:multidir错误 - 请参阅:https://docs.angularjs.org/error/$compile/multidir

答案 4 :(得分:6)

回答这个问题可能要迟到了。但是仍有人可能从我的答案中获益。

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

在我的控制器中:

       $("#Id").autocomplete().data("uiAutocomplete")._renderItem =  function( ul, item ) 
       {
         return $( "<li>" )
         .append( "<a>" + item.label + "<br>" + item.desc + "</a>" )
         .appendTo( ul );
       };

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

$scope.render=false;

//this will fire after load the the page

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

答案 5 :(得分:3)

我遇到了同样的问题,但使用Angular + DataTable和fnDrawCallback + row grouping + $编译的嵌套指令。我在我的fnDrawCallback函数中放置$ timeout来修复分页渲染。

在示例之前,基于row_grouping source:

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指令。

答案 6 :(得分:2)

对我来说,没有一个解决方案可以接受使用超时。这是因为我使用的是在postLink期间动态创建的模板。

但请注意,&#39; 0&#39;可能会超时。因为超时将被调用的函数添加到浏览器的队列中,该队列将在角度渲染引擎之后发生,因为它已经在队列中。

请参阅:http://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering

答案 7 :(得分: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)注意&#39; datatableSetup&#39;相当于&#39; datatable-setup&#39;。 Angular将格式更改为驼峰式。

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

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

答案 8 :(得分: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>

答案 9 :(得分: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。 我们还假设使用该指令的组件在指令之前被实例化,因为当实例化指令时,我们依赖于控制变量存在。