Angular ng-if и ng-show комбинация
Представьте себе какое-то тяжелое содержимое, которое может отображаться на веб-странице, например диаграмме.
Angular предоставляет 2 варианта переключения видимости указанного содержимого.
ng-show отобразит содержимое независимо от выражения и просто "спрячет" его после факта. Это не идеально, так как пользователь никогда не может "открыть" контент во время сеанса, поэтому для его рендеринга было ненужным.
ng-if лучше в этом отношении. Использование его вместо ng-show будет препятствовать тому, чтобы тяжелое содержимое отображалось в первую очередь, если выражение ложно. Однако его сила также является ее слабостью, потому что, если пользователь скрывает диаграмму, а затем показывает ее снова, содержимое визуализируется с нуля каждый раз.
Как я могу сделать директиву, которая берет лучшее из обоих миров? Это означает, что он работает как ng-if, пока контент не будет отображаться в первый раз, а затем переключится на работу как ng-show, чтобы предотвратить повторный рендеринг его каждый раз.
Ответы
Ответ 1
+1 на Denis отвечает, но только для полноты, его даже можно упростить, сохранив логику в представлении без "загрязняющего" контроллера:
<button ng-click="show = !show">toggle</button>
<div ng-if="once = once || show" ng-show="show">Heavy content</div>
plunker
EDIT: приведенная выше версия может быть дополнительно улучшена (и упрощена) с одноразовой привязкой, чтобы уменьшить ненужные $watch на once
- это будет работать только в Angular 1.3+:
<div ng-if="::show || undefined" ng-show="show">Heavy content</div>
undefined
необходим, чтобы гарантировать, что наблюдаемое значение не будет "стабилизировать" до того, как оно станет true
. Как только он стабилизируется, он также теряет $watch, поэтому на него не повлияет дальнейшее изменение на show
.
Ответ 2
Вы можете заставить его работать, используя ngIf
и ngShow
вместе, где каждый из них является контроллером с помощью другой переменной. ngIf
будет установлен в true
один раз и никогда не будет установлен на false
снова, а ngShow
будет переключаться столько, сколько хочет пользователь.
Посмотрите на fiddle.
Ответ 3
Это был ответ @new-dev, который вдохновил мою собственную идею сочетания, чтобы быстро получить довольно тяжелый компонент с помощью кнопки переключения.
Моя первоначальная проблема заключалась в том, что моя страница состоит из ~ 20 компонентов, каждый из которых занимает ~ 1 секунду для загрузки. Каждый из них переключается кнопкой.
Если вы используете простой ng-if, я бы получил быструю загрузку страницы и 1 секунду после переключения.
При использовании простого ng-show я получаю мгновенные переключающие нажатия, но задержка загрузки второй страницы...
Итак, теперь я совмещаю их с элементом управления ng-mouseenter. Вот так.
<div ng-mouseenter="create=true">
<button ng-click="showAll = !showAll"></button>
<!--lightweight content-->
<div ng-show="showAll">
<div ng-if="create">
<!--Heavy content-->
</div>
</div>
</div>
Это дало мне превосходную производительность как в Chrome, так и в IE. Время, необходимое пользователю для фактического нажатия кнопки после ввода компонента, используется для создания DOM. И затем узлы DOM быстро отображаются один раз (если) клик приходит.
Я также никогда не добавлял выражение ng-if в false. Хранение DOM кэшируется, если пользователь продолжает переключать этот раздел.
Обновление:
Причина, по которой у меня нет ng-if и ng-show в том же div, заключается в том, что в IE это вызывало мерцание. Когда событие mouseenter появилось, оно отобразило бы содержимое, а затем спрячет его. В хром было нормально иметь его в одном div.
Ответ 4
Вам может быть интересна пользовательская директива ng-lazy-show
от Алана Колвера. Он позволяет комбинировать как ng-if
, так и ng-show
:
<div ng-lazy-show="showFilters" lendio-business-filters></div>
Код небольшой и может быть легко добавлен в ваш проект:
var ngLazyShowDirective = ['$animate', function ($animate) {
return {
multiElement: true,
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
link: function ($scope, $element, $attr, $ctrl, $transclude) {
var loaded;
$scope.$watch($attr.ngLazyShow, function ngLazyShowWatchAction(value) {
if (loaded) {
$animate[value ? 'removeClass' : 'addClass']($element, 'ng-hide');
}
else if (value) {
loaded = true;
$transclude(function (clone) {
clone[clone.length++] = document.createComment(' end ngLazyShow: ' + $attr.ngLazyShow + ' ');
$animate.enter(clone, $element.parent(), $element);
$element = clone;
});
}
});
}
};
}];
angular.module('yourModule').directive('ngLazyShow', ngLazyShowDirective);