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

帅比萌擦擦* 提交于 2019-12-23 02:25:21

问题


Let's say we have some nested directives:

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

And let's say that big-poppa wants to create a component that all of his children directives can share. It would be nice to put it in the controller, but this component needs the DOM, so it needs to be build in the link function.

Then let's say the baby-bird component wants to read from component. Maybe it wants to listen to events from it, maybe send it a command or two. The challenge is that controllers fire down the dom (first parent, then child), and post-link methods fire the other direction, so the execution order looks like this:

  1. bigPoppa controller
  2. babyBird controller
  3. babyBird link
  4. bigPoppa link

The fact that the parent's link method fires after the child's is the cause of an intra-directive communication challenge for me. I want the parent to build the shared DOM component, but DOM construction should happen in a link function. The parent therefore builds the component after any children

I can solve this with a timeout (gross), or a promise (complex/non-idiomatic?). Here is the fiddle:

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

Lots of background here, but my question is this simple:

Is there a clean way to do behavior in a child directive after a parent's directive has run its post-link function?

Maybe a way of using priority, or the pre and post link methods?


回答1:


Another way of achieving this is to use plain Angular scope events to communicate from the parent linking function to the child.

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); 
      }); 
    }
  }
});

This can be seen working at http://jsfiddle.net/michalcharemza/kerptcrw/3/

This way has the benefits:

  • Has no scope watchers
  • Instead of depending on knowledge of the order of controller/pre-link/post-link phases, it uses a clear message send/receive paradigm, and so I would argue is easier to understand and maintain.
  • Doesn't depend on behaviour being in the pre-link function, which isn't that typical, and you have to be mindful to not put in behaviour that modifies the DOM in it.
  • Doesn't add variables to the scope hierarchy (but it does add events)



回答2:


Based on experiments, and asking correction if I am wrong, I have found Angular runs its compile phase in the following order:

  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)

The public gist of this experiment is here: https://gist.github.com/SimpleAsCouldBe/4197b03424bd7766cc62

With this knowledge, it seems like the pre-link callback on the parent directive is a perfect fit. The solution looks like this:

    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/

Thanks to @runTarm and this question for pointing me in the pre-link direction.




回答3:


There are 2 patterns that you can use to achieve what you want

  • You can have code in a child linking function that reacts to changes in a parent directive's controller, by requireing the parent directive's controller, and creating a $watcher on some value in it.

  • If you need run something in the parent linking function, and only then change a value in its controller, it is possible for a directive to require itself, and access the controller from the linking function.

Putting these together in your example becomes:

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);
      }); 
    }
  }
});

Which can be seen at http://jsfiddle.net/4L5bj/1/ . This has the benefits over your answer that it doesn't depend on scope inheritance, and doesn't pollute the scope with values that are only used by these directives.



来源:https://stackoverflow.com/questions/25144945/tell-child-directive-to-act-after-a-parent-directive-has-done-dom-actions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!