首页 文章

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

提问于
浏览
134

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

# 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

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

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

    在我的控制器中:

    $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>
    
  • 0

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

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

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

    Angular可以跟踪它操作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

  • 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 ...
                            }
                        });
    
                    }
                }
            }
        }
    }]);
    

    Note: 而不是仅仅在my-directive-watch属性中将myContent变量强制转换为bool,可以想象那里有任意表达式 .

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

  • 3

    我有同样的问题,但使用Angular DataTable与 fnDrawCallback row grouping $编译的嵌套指令 . 我将$ 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指令 .

  • 5

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

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

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

  • 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>

  • 14

    我使用以下指令:

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

    在HTML中:

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

    trouble shooting if the above doesnt work for you.

    1)注意'datatableSetup'相当于'datatable-setup' . Angular将格式更改为驼峰大小写 .

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

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

    由于无法预期负载顺序,因此可以使用简单的解决方案 .

    让我们来看看'指令'用户关系 . 通常,指令的用户将向指令提供一些数据或使用指令提供的一些功能(功能) . 另一方面,该指令期望在其范围内定义一些变量 .

    如果我们能够确保所有玩家在尝试执行这些操作之前满足所有操作要求 - 一切都应该很好 .

    现在指令:

    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 . 我们还假设使用该指令的组件在指令之前被实例化,因为我们依赖于控件变量在实例化指令时存在 .

相关问题