Подтвердить что ты не робот

$ postLink из компонента/директивы angular работает слишком рано

Обновление: Я поставил щедрость на этот вопрос. Я не ищу хаки или обходные пути. Я ищу официальный способ доступа к dom в компоненте angular и объяснение, почему поведение, которое я вижу ($ postLink работает до начала), кажется, противоречит официальным документам.
Официальные документы (здесь):

$postLink() - Вызывается после того, как этот элемент контроллера и его дочерние элементы связаны. Подобно функции post-link, этот крюк может использоваться для настройки обработчиков событий DOM и прямой манипуляции с DOM.

Оригинальный вопрос: У меня есть пример проблемы здесь → http://plnkr.co/edit/rMm9FOwImFRziNG4o0sg?p=preview

Я использую компонент angular, и я хочу изменить dom в функции post link, но он не работает, кажется, что функция запускается слишком рано, прежде чем шаблон будет фактически готов в dom после все angular обработки.

На странице html у меня есть следующее:

<my-grid grid-id="'foo'"></my-grid>

Компонент определяется как:

appModule.component('myGrid',{
    controller: gridController,
    bindings: {
        "gridId": "<",
    },
    templateUrl: 'gridTemplate'
});

В шаблоне компонента у меня есть следующее:

<table id='{{$ctrl.gridId}}'>
...

(Сам связывание работает, нет сомнений. В конечном итоге в html идентификатор таблицы "foo", как и ожидалось).

В контроллере у меня есть что-то вроде этого:

function gridController($scope, $compile, $attrs) {
    console.log ("grid id is: " + this.gridId); // 'foo'

    this.$postLink = function() {
        var elem = document.getElementById(this.gridId);
        // do something with elem, but elem is null
    }
}

То, что я вижу при отладке, заключается в том, что когда функция $postLink выполняется, таблица находится в dom, но ее атрибут id по-прежнему {{$ctrl.gridId}} вместо foo, поэтому document.getElementById() ничего не находит. Это похоже на документацию. Что мне не хватает? Есть ли другой способ доступа к dom в компоненте?

Обновление 2: Сегодня я понял, что такая же проблема возникает с регулярной ссылкой на директивы, она не ограничивается компонентами. Поэтому, по-видимому, я неправильно понял смысл "прямое манипулирование DOM" - функция ссылки работает на элементе, который отделен от dom, поэтому использование объекта document с селекторами бесполезно.

4b9b3361

Ответ 1

Документация относительно $postLink() верна. Он вызвал после того, как элемент контроллера и его дочерние элементы были связаны. Это не означает, что вы сразу увидите директивный результат. Возможно, он вызывает $http и вставляет результат после его поступления. Возможно, он регистрирует наблюдателя, который по очереди устанавливает результат, как это делает большинство встроенных директив Angular.

Основная проблема в вашем случае заключается в том, что вы хотите выполнять манипуляции с DOM после , скомпилированные интерполяции или еще лучше, после того, как их зарегистрированный наблюдатель успел запустить один раз.

К сожалению, официального способа сделать это не существует. Тем не менее, есть способы сделать это, но вы не найдете их в документации.

Два популярных способа запуска функции после компиляции интерполяций:

  • используя $timeout без задержки (по умолчанию 0): $timeout(function() { /* Your code goes here */ });

  • используя .ready(), который предоставляется Angular jqLite

В вашем случае вам гораздо лучше использовать наблюдателя для запуска функции, как только элемент с данным ID существует:

var deregistrationFn = $scope.$watch(() => {
    return document.getElementById(this.gridId);
}, (newValue) => {
    if (newValue !== null) {
        deregistrationFn();

        // Your code goes here
    }
});

Наконец, на мой взгляд, я считаю, что всякий раз, когда вам нужно ждать, чтобы скомпилированные интерполяции или некоторые директивы вставляли их значение, вы не следуете Angular способу построения вещей. В вашем случае, почему бы не создать новый компонент, myGridTable, который требует myGrid в качестве родителя и добавит туда свою соответствующую логику. Таким образом, ответственность каждого компонента намного лучше определена, и легче тестировать вещи.

Ответ 2

Непонимание в том, как цикл angular $digest работает в сочетании с событиями жизненного цикла контроллера (и событиями жизненного цикла директивы).

Каждый из событий жизненного цикла контроллера/директивы вызывается внутри $apply. Из-за этого DOM обновляется только для отражения изменений после завершения цикла $digest.

Функция post link запускается после того, как все было связано, но в конце этой функции DOM обновляется, чтобы отражать любые привязки, из-за того, что он был последняя функция выполняется внутри цикла $digest. Это означает, что попытка запросить DOM для чего-либо, связанного с привязкой, не будет работать (например, ng-repeat или в вашем случае - связанное значение). Но, как указано в документах, связь DOM уже запущена, поэтому исходный шаблон был создан и может быть обработан.

Поскольку вам нужно запросить DOM после того, как были применены привязки, вам нужно запустить код, после того, как закончится текущий цикл $digest. Это означает использование $timeout с задержкой 0, которая будет выполнена сразу после окончания текущего цикла $digest.

function gridController($scope, $compile, $attrs, $timeout) {
    console.log ("grid id is: " + this.gridId); // 'foo'

    this.$postLink = function() {
        $timeout(function() {
            var elem = document.getElementById(this.gridId);
            // do something with elem now that the DOM has had it bindings applied
        });
    }
}

Ответ 3

Кажется, что он имеет отношение к пониманию сценариев компонентов angular, прежде чем давать какое-либо решение. И что я вижу из вашего примера шаблон DOM, загруженный в область изолированная. Повторите выделенную область

Итак,

Согласно официальному документу [1.5.6] на компонентах https://docs.angularjs.org/guide/component

Если не использовать Компоненты:

для директив, которые полагаются на манипуляции с DOM, добавление прослушивателей событий и т.д. , поскольку функции компиляции и ссылки недоступны когда вам нужны расширенные параметры определения директивы, такие как приоритет, терминал, многоэлементный если вы хотите, чтобы директива запускалась атрибутом или классом CSS, а не элементом

Если вы просмотрите раздел "Сравнение между определением директивы и определением компонентов" из документа, вы увидите

функция ------------ директива ----------- компонент привязки ------------ Нет ------------------- Да (привязывается к контроллеру) bindToController ---- Да (по умолчанию: false) -Нет (вместо этого используйте привязки) функция компиляции ---- Да ------------------ Нет ---------- Да ------------------ Да (функция по умолчанию() {}) controllerAs -------- Да ------------------ (по умолчанию: false) Да (по умолчанию: $ctrl) Функции ссылок ------ Да ------------------ Нет

Теперь

Компоненты никогда не должны изменять какие-либо данные или DOM, которые находятся вне их . Обычно в angular можно изменять данные в любом месте в приложении через область наследование и часы. Это но может также привести к проблемам, когда неясно, часть приложения отвечает за изменение данных. То есть почему директивы компонентов используют область изолировать, поэтому целый класс манипуляция по объему невозможна. REF: ng-doc 1.5.6

Но когда вы видите, есть доступ к объявлению ad $postLink(), вы можете задаться вопросом, в чем причина?

Однако, если вы просмотрите описание этого крючка, вы найдете...

Примечание, что дочерние элементы, содержащие директивы templateUrl, не будут скомпилированы и связаны, поскольку они ждут чтобы их шаблон загружался асинхронно, а их собственная компиляция и привязка были приостановлены до тех пор, пока это не произойдет.

Пожалуйста, обратитесь к ответам Стивена Ламберта ниже...