...

вторник, 10 июня 2014 г.

AngularJS — Вы уверены, что знаете как работает ng-if?



Не так давно я уже писал про поведение ng-if директивы, но тогда я столкнулся с проверкой условия, но сегодня возникла другая проблема.

В проекте достаточно много таких элементов как tooltip, popover, modal windows и так далее. Думаю, все вы понимаете, что это за элементы и рассказывать про них я не буду. Для многих из них используется абсолютное позиционирование. Если бы мы не использовали кастомные директивы, то проблем бы не было — все модальные окна лежали бы в конце body и показывались бы когда нужно. Но так, как все эти элементы объявлены как директивы, возникает проблема с позиционированием, так как у директивы может быть родитель с относительным позиционированием и так далее.



<div style="position: relative; overflow:hidden">
<button ng-click="visible = true">Greeting</button>

<modal visible="visible">
Hello, Habr!
</modal>
</div>






Модальное окно должно позиционироваться относительно окна браузера, но в данном случае будет позиционироваться относительно родительского элемента.

Самое простое решение этой проблемы — вынести элемент из текущей директивы:

module.directive('modal',[
'$rootElement',
function(
$rootElement
){
return {
restrict: 'E',
...
link: function(scope, element){
element.appendTo($rootElement);

scope.$on('$destroy', function(){
element.remove();
});

...
}
}
}]
});


То есть мы выдераем элемент из текущего контекста и вставляем в рутовый элемент приложения. При удаление директивы — элемент удаляется. Все вроде ОК, но проблем не возникает до тех пор, пока в паре с таким подходом не используется ng-if директива.


ng-if при отрицательном результате условия полностью удаляет DOM элемент, это я думаю многие знают, но не многие знают как это происходит.


Вот исходники и собственно сам watcher ng-if атрибута.

При положительном результате — создается комментарий document.createComment(' end ngIf: ' + $attr.ngIf + ' '); и в переменную block.clone помещается два значения:



  • 0 — сам элемент, для которого была объявлена ng-if директива

  • 1 — созданный комментарий


В исходном коде страницы вы скорее всего часто видите подобное:



На данном скриншоте — условие ng-if="!task.id" — положительное и элемент li, для которого объявлена директива есть в DOM дереве и находиться между комментариями <!-- ngIf: !task.id --> и <!-- end ngIf: !task.id -->. Второе условие ng-if="validation.task.app_id" — отрицательное и между комментариями нету ничего.





При отрицательном результате — destroy дочернего scope и удаление элементов. И самое интересное в функции getBlockElements:

/**
* Return the DOM siblings between the first and last node in the given array.
* @param {Array} array like object
* @returns {DOMElement} object containing the elements
*/
function getBlockElements(nodes) {
var startNode = nodes[0],
endNode = nodes[nodes.length - 1];
if (startNode === endNode) {
return jqLite(startNode);
}

var element = startNode;
var elements = [element];

do {
element = element.nextSibling;
if (!element) break;
elements.push(element);
} while (element !== endNode);

return jqLite(elements);
}




Что делает эта функция понятно из её описания — Return the DOM siblings between the first and last node in the given array.

А аргумент nodes в нашем случаем массив из двух элементов, которые я описывал ваше. То есть функция вернет все элементы между основным элементом, для которого была объявлена директива ng-if и закрывающим комментарием <!-- eng ngIf: .... -->, а если комментарий не был найден — то вернет все элементы после основного.

К примеру, такой темплейт (#angular-application — рутовый элемент приложения):



<div id="angular-application">
...

<div style="position: relative; overflow: hidden">
<div style="position: absolute; right: 0; bottom: 0">
<modal ng-if="isFirstModal()" id="modal-1">...</modal>
<modal ng-if="isSecondModal()" id="modal-2">...</modal>
</div>
<div style="position: absolute; left: 0; bottom: 0">
<popover ng-if="isFirstPopover()" id="popover-1">...</popover>
<popover ng-if="isSecondPopover()" id="popover-2">...</popover>
</div>
</div>

...
</div>




Компилится в такой html:

<div id="angular-application">
...

<div style="position: relative; overflow: hidden">
<div style="position: absolute; right: 0; bottom: 0">
<!-- ngIf: isFirstModal() -->
<!-- end ngIf: isFirstModal() -->
<!-- ngIf: isSecondModal() -->
<!-- end ngIf: isSecondModal() -->
</div>
<div style="position: absolute; left: 0; bottom: 0">
<!-- ngIf: isFirstPopover() -->
<!-- end ngIf: isFirstPopover() -->
<!-- ngIf: isSecondPopover() -->
<!-- end ngIf: isSecondPopover() -->
</div>
</div>

...

<div id="popover-1" class="popover">...</div>
<div id="modal-1" class="modal-window">...</div>
<div id="modal-2" class="modal-window">...</div>
<div id="popover-2" class="popover">...</div>
</div>




То есть, как было написано выше — все модальные окна и поповеры, что бы не нарушалось позиционирование и их верстка перенесены в конце приложения, но комментарии остались на прежнем месте. И теперь, функция getBlockElements для <popover ng-if="isFirstPopover()" id="popover-1">.вернет все элементы#popover-1,#modal-1, #modal-2, #popover-2. То есть при отрицательном результате условия ng-if="isFirstPopover()" из DOM дерева будут удалены все эти элементы.

Варианты решения:


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий