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);