Angular.js源码分析之ngif
在angular中有两个内置的指令ng-if
和ng-repeat
是比较特殊的,特殊之处在于他们创建指令时候多了一个内置参数配置:$$tlb
。ng-if
相对ng-repeat
逻辑则更加简单,分析起来也就更容易。所以这里就来分析分析ng-if
指令,目的是不仅要知道了ng-if
指令的逻辑,也要知道这个参数为啥会存在。
先来看下源码:
var ngIfDirective = ['$animate', function($animate) {
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.ngIf, function ngIfWatchAction(value) {
if (value) {
// true就是要显示
if (!childScope) {
// 依旧是调用$transclude
$transclude(function(clone, newScope) {
childScope = newScope;
// 插入注释元素
clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
// 保留引用
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).then(function() {
previousElements = null;
});
block = null;
}
}
});
}
};
}];
从上边代码可以看出其基本逻辑还是很简单的,但是不明白的是这个私有的设置参数$$tlb
到底是用来干嘛的,有啥用处吗?是为了解决什么样的问题的(当然不建议开发者使用)?
我觉得不如首先来看没有这个配置会出现什么问题呢?做一个实验,把ngIfDirective
的源码中的$$tlb
去掉,然后来一个简单的demo,一探究竟。
<div ng-app="app">
<button ng-click="toogleV()">toggle visibility</button>
<div ng-if="showed" class="ifEle" ng-include="url">
<p ng-class="{showed:showed}">ifEle content</p>
</div>
</div>
<script type="text/javascript">
var app = angular.module('app', []);
app.run(['$rootScope', function($rootScope) {
$rootScope.showed = false
$rootScope.url = './include.html'
$rootScope.toogleV = function () {
$rootScope.showed = !$rootScope.showed;
};
}])
</script>
include.html内容:
<div ng-class="{showed:showed}">include content</div>
注意上面是在修改后的情况下,打开后发现报错了,这样的错误:
Error: [$compile:multidir] Multiple directives [ngIf, ngInclude] asking for transclusion on: <div ng-if="showed" class="ifEle" ng-include="url">
这样的判断出自:
if (!directive.$$tlb) {
assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
nonTlbTranscludeDirective = directive;
}
// 省略
function assertNoDuplicate(what, previousDirective, directive, element) {
function wrapModuleNameIfDefined(moduleName) {
return moduleName ?
(' (module: ' + moduleName + ')') :
'';
}
if (previousDirective) {
throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
}
}
而且transclude节点的时机还是在link之后,去增加或者替换他们。但是实际的情况就是上边demo的场景还是有可能出现的,所以说angular为了解决这个问题加入了私有配置$$tlb
,而且仅限于两个指令ng-if
和ng-repeat
。
话说回来,有了这个属性,angular就不去检查多个transclude
的情况了,就上边的例子来说,究竟是怎样的执行过程呢?从页面结果来看呢,最终执行的结果是显示的同级的include.html
的内容。
其实应该拆分来理解,先理解ng-if
的指令,他的优先级比较高,所以说是先执行,通过变换并且创建了注释节点,transclude完成之后此时在页面上会展示include处理得到的注释节点(tranclude处理逻辑);然后第二步拿到了in-include
的内容后依旧是进行transclude,完成后显示的是原始内容:<p ng-class="{showed:showed}">ifEle content</p>
,从之前在ngInclude分析中我们已经知道其实还有一个步骤那就是ngIncludeFillContentDirective
的处理,最终的结果就是会给当前的这个ifEle
设置innerHTML为得到的模板内容(也就是<div ng-class="{showed:showed}">include content</div>
),然后compile和link。
结语
从上边的分析可以知道了$$tlb
就是用来指定在compile的时候不要check同一个元素被多次transclude。而ng-if
指令本身的逻辑还是比较简单的:监测ng-if
属性的值,如果为true了那么就$transclude
得到内容,否则的话就移除掉。