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() 调用中以提供表数据,或者您可以将数据已经在页面上,它会完成剩下的工作。我正在做后者,在 HTML 页面上已经有了行.

但问题是我必须在 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 中看到了“嘘”。

然后我尝试我的 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') %>"
    }

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

4

10 回答 10

217

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

$timeout(function () {
    //DOM has finished rendering
});
于 2014-03-20T17:43:39.280 回答
14

我有同样的问题,我相信答案是否定的。请参阅Miško 的评论和小组中的一些讨论

Angular 可以跟踪它为操作 DOM 所做的所有函数调用是否已完成,但由于这些函数可能会触发异步逻辑,这些逻辑在它们返回后仍在更新 DOM,因此不能期望 Angular 知道它。Angular 提供的任何回调有时可能会起作用,但依赖它并不安全。

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

(请记住,不是每个人都同意我的观点——你应该阅读上面链接上的评论,看看你的想法。)

于 2012-09-28T14:16:46.950 回答
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

于 2012-06-20T18:58:21.130 回答
7

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

与其实现超时,不如添加一个监听内容更改(甚至是额外的外部触发器)的手表。

在我的例子中,一旦 ng-repeat 完成创建了我的内部 DOM,我就使用这个解决方法来初始化一个 jQuery 插件——在另一种情况下,我用它来只是在控制器改变范围属性后操作 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 属性的 bool 之外,还可以想象那里的任意表达式。

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

于 2014-06-01T11:30:05.873 回答
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>
于 2015-05-21T08:50:28.380 回答
3

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

在示例之前,基于 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 指令。

于 2013-01-30T19:57:47.310 回答
2

没有任何解决方案对我有用,因为我接受了超时。这是因为我使用的是在 postLink 期间动态创建的模板。

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

参考这个:http ://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering

于 2015-01-21T10:51:47.780 回答
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>

于 2015-03-11T04:21:15.367 回答
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(); } }
});
于 2013-04-26T14:37:28.787 回答
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。我们还假设使用指令的组件在指令之前被实例化,因为我们依赖于在指令被实例化时存在的控制变量。

于 2016-04-26T07:14:28.487 回答