Как реализовать обратное однократное связывание выражения ng-if в AngularJS? - PullRequest
0 голосов
/ 30 августа 2018

У меня есть пользовательский компонент AngularJS, который может использоваться на одной веб-странице более 200 раз. Страница в конечном итоге реализует более 4000 наблюдателей - это больше, чем рекомендуемое максимальное количество наблюдателей AngularJS - и делает страницу действительно медленной.

Фактическая проблема заключается в том, что в шаблоне компонента осталось много ненужных наблюдателей от некоторых ng-if и других выражений AngularJS, которые больше не будут менять свои значения.

Для нормальных ng-if исправить было легко:

<div ng-if="::$ctrl.isInitialized()">Ready!</div>

... где $ctrl.isInitialized() вернет либо true (когда компонент был инициализирован), либо undefined (до тех пор, пока он не был).

Возвращение undefined здесь заставит AngularJS поддерживать наблюдатель активным, пока он не возвратит что-то еще, в этом случае значение true, а затем добавит div в DOM.

Нет ng-not="expression", как есть ng-hide. Это хорошо работает с ng-hide, за исключением того, что, конечно, div все еще находится в DOM после инициализации контроллера, что не является идеальным решением.

Но как вы можете реализовать это так, чтобы <div> находился в DOM до тех пор, пока контроллер не будет инициализирован, и будет удален после?

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

вид быстрого патча: angular допускает троичный оператор в выражениях после v1.1.5, я думаю.

Так что вы можете сделать что-то вроде:

<div ng-if="::$ctrl.isInitialized() === undefined? undefined: !$ctrl.isInitialized()">

Насколько я вижу, undefined не имеет особого значения в угловом выражении - он рассматривается как другая (еще не определенная) переменная в $scope. Поэтому я должен был указать это явно:

$scope = undefined;

Альтернативным вариантом является написание короткого помощника:

function isDefined(val) {
    return angular.isDefined(val) || undefined;
}

Чтобы использовать его позже как

ng-if="::isDefined($ctrl.isInitialized()) && !$ctrl.isInitialized()"

Но поскольку вы говорите, что для этого слишком много мест - наверняка создание собственного компонента, как вы кодировали выше, выглядит лучше

0 голосов
/ 31 августа 2018

Хотя директивы ng-not нет, ее было легко реализовать из исходного кода AngularJS:

var ngNotDirective = ['$animate', '$compile', function($animate, $compile) {

  function getBlockNodes(nodes) {
    // TODO(perf): update `nodes` instead of creating a new object?
    var node = nodes[0];
    var endNode = nodes[nodes.length - 1];
    var blockNodes;

    for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
      if (blockNodes || nodes[i] !== node) {
        if (!blockNodes) {
          blockNodes = jqLite(slice.call(nodes, 0, i));
        }
        blockNodes.push(node);
      }
    }

    return blockNodes || nodes;
  }

  return {
    multiElement: true,
    transclude: 'element',
    priority: 600,
    terminal: true,
    restrict: 'A',
    $$tlb: true,
    link: function($scope, $element, $attr, ctrl, $transclude) {
      var block, childScope, previousElements;
      $scope.$watch($attr.ngNot, function ngNotWatchAction(value) {
        if (!value) {
          if (!childScope) {
            $transclude(function(clone, newScope) {
              childScope = newScope;
              clone[clone.length++] = $compile.$$createComment('end ngNot', $attr.ngNot);
              // Note: We only need the first/last node of the cloned nodes.
              // However, we need to keep the reference to the jqlite wrapper as it might be changed later
              // by a directive with templateUrl when its template arrives.
              block = {
                clone: clone
              };
              $animate.enter(clone, $element.parent(), $element);
            });
          }
        } else {
          if (previousElements) {
            previousElements.remove();
            previousElements = null;
          }
          if (childScope) {
            childScope.$destroy();
            childScope = null;
          }
          if (block) {
            previousElements = getBlockNodes(block.clone);
            $animate.leave(previousElements).done(function(response) {
              if (response !== false) previousElements = null;
            });
            block = null;
          }
        }
      });
    }
  };
}];

Это та же реализация, что и ng-if, за исключением того, что она вернула if (!value) check.

Может использоваться следующим образом:

<div ng-not="::$ctrl.isInitialized() ? true : undefined">Loading...</div>

Легко проверить, что бесполезных наблюдателей нет, добавив console.log() в $ctrl.isInitialized() - эта функция будет вызываться всего несколько раз, пока она не вернет true и наблюдатель будет удален, а также div и все, что внутри него.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...