如何在AngularJS中实现反向一次绑定ng-if表达式?

时间:2018-08-30 17:45:41

标签: javascript angularjs

我有一个自定义的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 :(得分:0)

尽管没有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)检查。

它可以这样使用:

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

通过在console.log()中添加$ctrl.isInitialized()可以很容易地验证是否有无用的观察者-该函数将被调用几次,直到返回true且观察者为已删除-以及div及其内部的所有内容。

答案 1 :(得分:0)

一种快速修补程序:我想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()"

但是由于您说的地方太多了-请确保按照上面的代码制作自己的组件看起来更好