Скажите дочерним директивам действовать после того, как родительские директивы выполнили действия DOM?
Допустим, у нас есть несколько вложенных директив:
<big-poppa>
<baby-bird></baby-bird>
</big-poppa>
И скажем так big-poppa
хочет создать компонент, которым могут поделиться все его дочерние директивы. Было бы неплохо поместить его в контроллер, но этот компонент нуждается в DOM, поэтому его необходимо встроить в функцию link.
Тогда скажем baby-bird
компонент хочет читать из компонента. Может быть, он хочет слушать события из него, может быть, отправить ему команду или две. Проблема в том, что контроллеры запускают dom (сначала родитель, затем потомок), а методы post-link запускают в другом направлении, поэтому порядок выполнения выглядит следующим образом:
- контроллер BigPoppa
- контроллер babyBird
- ссылка babyBird
- ссылка BigPoppa
Тот факт, что родители link
метод срабатывает после того, как ребенок стал причиной интра-директивного вызова для меня. Я хочу, чтобы родительский компонент создал общий компонент DOM, но создание DOM должно происходить в функции ссылки. Поэтому родительский компонент строит компонент после любых дочерних элементов.
Я могу решить это с помощью таймаута (брутто) или обещания (сложного / не идиоматического?). Вот скрипка:
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);
}
}
});
Спасибо @runTarm и этот вопрос, чтобы указать мне в pre-link
направление.