Скажите дочерним директивам действовать после того, как родительские директивы выполнили действия DOM?

Допустим, у нас есть несколько вложенных директив:

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

И скажем так big-poppa хочет создать компонент, которым могут поделиться все его дочерние директивы. Было бы неплохо поместить его в контроллер, но этот компонент нуждается в DOM, поэтому его необходимо встроить в функцию link.

Тогда скажем baby-bird компонент хочет читать из компонента. Может быть, он хочет слушать события из него, может быть, отправить ему команду или две. Проблема в том, что контроллеры запускают dom (сначала родитель, затем потомок), а методы post-link запускают в другом направлении, поэтому порядок выполнения выглядит следующим образом:

  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, или pre а также post методы ссылки?

3 ответа

Другой способ достижения этого - использовать простые события области видимости 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 в нем.
  • Не добавляет переменные в иерархию области (но добавляет события)

Есть 2 модели, которые вы можете использовать, чтобы достичь того, что вы хотите

  • У вас может быть код в дочерней функции связывания, который реагирует на изменения в контроллере родительской директивы: 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/. Это дает преимущества перед вашим ответом: он не зависит от наследования области и не загрязняет область значениями, которые используются только этими директивами.

На основании экспериментов и запроса исправления, если я не прав, я обнаружил, что 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 направление.

Другие вопросы по тегам