Как выполнить родительскую директиву перед дочерней директивой?

Я хочу написать две угловые директивы, родительскую и дочернюю директиву, чтобы создать сортируемые и клонируемые виджеты. Предполагаемая разметка:

<div class="widget-container" data-sortable-widgets>
      <section class="widget" data-cloneable-widget></section>
<div>

Однако дочерняя директива, похоже, выполняется перед родителем, прежде чем какой-либо элемент будет доступен (добавлен родителем):

function SortableWidgetsDirective() {
    return {
        priority: 200,
        restrict: 'A',
        link: function ($scope, element, attrs) {
            element.find(".widget header").append($("<div class='widget-controls'></div>"));
            element.sortable({  });
        }
    };
}

function CloneableWidgetDirective() {
    return {
        priority: 100,
        restrict: 'A',
        link: function ($scope, element, attrs) {
            // This directive wrongfully executes first so widget-controls is no available
            element.find("header .widget-controls").append($("<div class='clone-handle'></div>"));
        }
    };
}

Как вы можете видеть, я попытался установить приоритет, но я думаю, потому что они на разных элементах, это не работает.

Как я могу заставить родительский выполнить первый?

Ответ 1

аргументация

postLink() выполняется в обратном порядке, что означает, что postLink() директива postLink() будет вызываться перед родительским (т.е. сначала первой). По какой-то причине это поведение по умолчанию (link() самом деле относится к postLink()). К счастью, у нас также есть preLink(), который работает наоборот - мы можем использовать это в свою пользу.

Чтобы проиллюстрировать это - следующий фрагмент кода:

app.directive('parent', function($log) {
    return {
        restrict: 'E',
        compile: function compile(tElement, tAttrs, transclude) {
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {
                    $log.info('parent pre');
                },
                post: function postLink(scope, iElement, iAttrs, controller) {
                    $log.info('parent post');
                }
            }
        }
    };
});

app.directive('child', function($log) {
    return {
        restrict: 'E',
        compile: function compile(tElement, tAttrs, transclude) {
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {
                    $log.info('child pre');
                },
                post: function postLink(scope, iElement, iAttrs, controller) {
                    $log.info('child post');
                }
            }
        }
    };
});

... выводит следующее:

> parent pre
> child pre
> child post
> parent post 

См. live on plunker.

Решение

Если мы хотим, чтобы родительская директивная логика выполнялась перед дочерним preLink(), мы будем явно использовать preLink():

function SortableWidgetsDirective() {
    return {
        restrict: 'A',
        compile: function compile(tElement, tAttrs, transclude) {
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {
                    iElement.find(".widget header").append($("<div class='widget-controls'></div>"));
                    iElement.sortable({});
                },
                post: angular.noop
            }
        }
    };
}

function CloneableWidgetDirective() {
    return {
        restrict: 'A',
        compile: function compile(tElement, tAttrs, transclude) {
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {
                    iElement.find("header .widget-controls").append($("<div class='clone-handle'></div>"));
                },
                post: angular.noop
            }
        }
    };
}

Рекомендации


Пост скриптум

  • Вы, кстати, правы - priority предназначен для использования с директивами, которые имеют один и тот же элемент.

  • angular.noop - это пустой метод, который ничего не возвращает. Если вы все еще хотите использовать функции postLink(), просто поместите объявление функции, как обычно, например:

    post: function postLink(scope, iElement, iAttrs, controller) { ... }
    
  • Будьте использовать templateUrl, так как "поскольку загрузка шаблона асинхронна, компиляция/привязка приостановлена до тех пор, пока шаблон не будет загружен" [source]. В результате порядок исполнения будет нарушен. Вы можете исправить это, включив в него шаблон, встроенный в свойство template.