告诉child指令在父指令完成DOM操作后执行操作?

时间:2014-08-05 17:34:25

标签: javascript angularjs angularjs-directive angularjs-compile

假设我们有一些嵌套指令:

<big-poppa>
  <baby-bird></baby-bird>
</big-poppa>

让我们说big-poppa想要创建一个他所有的孩子指令可以共享的组件。将它放在控制器中会很好,但是这个组件需要DOM,所以它需要在链接函数中构建。

然后让我们说baby-bird组件想要从组件中读取。也许它想要听取它的事件,或许发送一两个命令。挑战是控制器触发dom(第一个父节点,然后是子节点),后链接方法触发另一个方向,因此执行顺序如下所示:

  1. bigPoppa controller
  2. babyBird控制器
  3. babyBird链接
  4. bigPoppa链接
  5. 父母的link方法在孩子之后触发的事实是导致我的内部指令通信挑战的原因。我希望父级构建共享DOM组件,但DOM构造应该在链接函数中进行。因此父母在任何孩子之后构建组件

    我可以用超时(粗略)或承诺(复杂/非惯用?)来解决这个问题。这是小提琴:

    http://jsfiddle.net/8xF3Z/4/

        var app = angular.module('app',[]);
    
    
        app.directive('bigPoppa', function($q){
            return {
                restrict: 'E',
                controller: function($scope){
                    console.log('bigPoppa controller');
                    var d = $q.defer()
                    $scope.bigPoppaLinkDeferred = d
                    $scope.bigPoppaLink = d.promise
                },
                link: function(scope, el, attrs){
                    console.log('bigPoppa link');
                    scope.componentThatNeedsDom = { el: el, title: 'Something' };
                    scope.bigPoppaLinkDeferred.resolve()
                }
            }
        });
    
        app.directive('babyBird', function(){
            return {
                restrict: 'E',
                controller: function(){ console.log('babyBird controller'); },
                link: function(scope, el, attrs, bigPoppaController){
                    console.log('babyBird link');
    
                    // console.log('poppa DOM component', scope.componentThatNeedsDom); // Not yet defined, because the parent's link function runs after the child's
    
                    // setTimeout(function(){ console.log('poppa DOM component', scope.componentThatNeedsDom); }, 1); // Works, but gross
    
                    scope.bigPoppaLink.then(function(){
                      console.log('poppa DOM component', scope.componentThatNeedsDom);
                    }); // works, but so complex!
    
                }
            }
        });
    
        console.log(''); // blank line
    

    这里有很多背景,但我的问题很简单:

      

    在父指令运行其后链接功能后,是否有一种干净的方式在子指令中执行行为?

    也许使用priorityprepost链接方法?

3 个答案:

答案 0 :(得分:1)

实现此目的的另一种方法是使用普通的Angular范围事件从父链接函数传递给子元素。

var app = angular.module('app',[]);

app.directive('bigPoppa', function($q){
  return {
    restrict: 'E',
    link: function(scope, el, attrs){
      scope.$broadcast('bigPoppa::initialised',  {el: el, title: 'Something'});
    }
  }
});

app.directive('babyBird', function(){
  return {
   restrict: 'E',
    link: function(scope, el, attrs) {
      scope.$on('bigPoppa::initialised', function(e, componentThatNeedsDom) {
        console.log('poppa DOM component in bird linking function', componentThatNeedsDom); 
      }); 
    }
  }
});

可以看到http://jsfiddle.net/michalcharemza/kerptcrw/3/

这种方式有以下好处:

  • 没有范围观察员
  • 它不是依赖于控制器/预链接/后链接阶段的顺序的知识,而是使用明确的消息发送/接收范例,因此我认为更容易理解和维护。
  • 不依赖于预链接功能中的行为,这不是典型的行为,您必须注意不要在其中添加修改DOM的行为。
  • 不会在范围层次结构中添加变量(但会添加事件)

答案 1 :(得分:0)

根据实验,如果我错了要求更正,我发现Angular按以下顺序运行其编译阶段:

  1. compile methods of all directives both parent and child, run in flat order
  2. parent controller
  3. parent pre-link
  4. (all actions of children directives)
  5. parent post-link (AKA regular `link` function)

此实验的公开要点是:https://gist.github.com/SimpleAsCouldBe/4197b03424bd7766cc62

有了这些知识,似乎父指令的pre-link回调非常合适。解决方案如下所示:

    var app = angular.module('app',[]);

    app.directive('bigPoppa', function($q){
        return {
            restrict: 'E',
            compile: function(scope, el) {
                return {
                    pre:  function(scope, el) {
                      console.log('bigPoppa pre');
                      scope.componentThatNeedsDom = { el: el, title: 'Something' };
                    }
                };
            }
        }
    });

    app.directive('babyBird', function(){
        return {
            restrict: 'E',
            link: function(scope, el, attrs, bigPoppaController){
                console.log('babyBird post-link');
                console.log('bigPoppa DOM-dependent component', scope.componentThatNeedsDom);
            }
        }
    });

http://jsfiddle.net/a5G72/1/

感谢@runTarm和this question将我指向pre-link方向。

答案 2 :(得分:0)

您可以使用两种模式来实现您想要的效果

  • 您可以在子链接函数中使用代码来响应父指令控制器中的更改,通过require父指令的控制器,并在某个值上创建$watch

  • 如果您需要在父链接函数中运行某些内容,然后只更改其控制器中的值,则指令可以require 本身,并且可以访问来自链接功能的控制器。

将这些放在你的例子中变成:

var app = angular.module('app',[]);

app.directive('bigPoppa', function($q){
  return {
    restrict: 'E',
    require: 'bigPoppa',
    controller: function($scope) {
      this.componentThatNeedsDom = null;
    },
    link: function(scope, el, attrs, controller){
      controller.componentThatNeedsDom = { el: el, title: 'Something' };
    }
  }
});

app.directive('babyBird', function(){
  return {
    restrict: 'E',
    require: '^bigPoppa',
    link: function(scope, el, attrs, bigPoppaController){
      scope.$watch(function() {
          return bigPoppaController.componentThatNeedsDom
      }, function(componentThatNeedsDom) {
          console.log('poppa DOM component in bird linking function', componentThatNeedsDom);
      }); 
    }
  }
});

可以在http://jsfiddle.net/4L5bj/1/看到。这样做的好处就在于它不依赖于范围继承,并且不会使用仅由这些指令使用的值来污染范围。