告诉子指令在父指令完成DOM操作之后执行操作

Tell child directive to act after a parent directive has done DOM actions?

本文关键字:指令 操作 DOM 之后 执行      更新时间:2023-09-26

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

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

假设big-poppa想要创建一个所有子指令都可以共享的组件。把它放在控制器中会很好,但这个组件需要DOM,所以它需要在链接函数中构建。

假设baby-bird组件想要从组件中读取。也许它想从中听到事件,也许向它发送一两个命令。挑战在于,控制器触发dom(首先是父对象,然后是子对象(,后链接方法触发另一个方向,因此执行顺序如下:

  1. bigPoppa控制器
  2. babyBird控制器
  3. babyBird链接
  4. bigPoppa链接

父级的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

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

在父指令运行其post-link函数后,是否有一种干净的方法可以在子指令中执行行为?

也许是使用priority的一种方式,或者prepost链接方法?

实现这一点的另一种方法是使用纯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/

这种方式有好处:

  • 没有范围观察者
  • 它使用了一个清晰的消息发送/接收范式,而不是依赖于控制器/链接前/链接后阶段的顺序,因此我认为它更容易理解和维护
  • 不依赖于pre-link函数中的行为,这不是典型的,并且您必须注意不要在其中放入修改DOM的行为
  • 不向作用域层次结构添加变量(但添加事件(

基于实验,如果我错了,我会要求更正,我发现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和这个问题为我指明了pre-link的方向。

有两种模式可以用来实现您想要的

  • 通过require调用父指令的控制器,并在其中的某个值上创建一个$watcher,可以在子链接函数中包含对父指令控制器中的更改做出反应的代码。

  • 如果您需要在父链接函数中运行一些东西,然后才更改其控制器中的值,则可以向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/。这比您的答案有好处,因为它不依赖于范围继承,并且不会用仅由这些指令使用的值污染范围。