首先我们来和replace进行一个对比:
这里是指令内部的内容。
我们看看replace中的代码:
var myModule = angular.module("MyModule", []);
myModule.directive("hello", function() {
return {
restrict:"AE",
template:"Hello everyone!",
replace:true
}
});
这时候我们很清楚的看到下面的DOM结构:
也就是说我们的template内容把
我们使用transclude来完成标签的嵌套工作:
这里是指令内部的内容。
我们看看transcude在内部是如何完成的:
var myModule = angular.module("MyModule", []);
myModule.directive("hello", function() {
return {
restrict:"AE",
transclude:true,
template:"Hello everyone!"
}
});
很显然首先需要把transclude设置为true,然后在template中进行修改,
其中就是告诉ng把hello标签内部的内容全部放在ng-transclude所在的位置。这就比较容易理解了,这里的hello标签没有被替换掉,因为没有指定replace:true,至于内部
还是会原封不动的保存,只是内部原来的DOM结构被这个ng-transclude指定的div包裹起来了!而且内部原来的DOM也被添加了一个内置的class,也就是ng-scope,这一点一定要注意!我们在最后给出一个综合的例子:
{{text}}
我们看看expander指令的签名:
var expanderModule=angular.module('expanderModule', []);
//expander指令
expanderModule.directive('expander', function() {
return {
restrict : 'EA',
replace : true,
//直接把expander替换掉了
transclude : true,
//transclude表示可以嵌套标签
scope : {
title : '=expanderTitle'
//这里是双向绑定,使用了'='进行双向绑定!
},
//直接替换掉,ng-show通过参数'showMe'来决定是否显示。ng-transclude指定了就是expander内部添加到ng-transclude中
template : ''
+ '{{title}}'
+ ''
+ '',
//这里指定link函数
link : function(scope, element, attrs) {
scope.showMe = false;
scope.toggle = function() {
scope.showMe = !scope.showMe;
}
}
}
});
//这里指定controller控制器
expanderModule.controller('SomeController',function($scope) {
//在$scope中指定title和text
$scope.title = '点击展开';
$scope.text = '这里是内部的内容。';
});
至此,我们的transclude就可以理解了。
<在控制器中注入的$transclude服务>
我们看看内部是如何注入$transclude服务的:
var testapp = angular.module('testapp', []);
testapp.controller('parentController', ['$scope', '$window', function($scope, $window) {
$scope.onPrimary1Click = function() {
alert('Primary1 clicked');
};
$scope.primary1Label = "Prime1"
}]);
testapp.directive('primary', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
element.addClass('btn btn-primary');
}
}
});
testapp.directive('secondary', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
element.addClass('btn');
}
}
});
testapp.directive('buttonBar', function() {
return {
restrict: 'EA',
template: '',
replace: true,
transclude: true,
scope: {},//这里是独立作用域
//我们在这里注入了$scope,$element,$transclude。这个控制器应该是指令的控制器
controller: ['$scope', '$element', '$transclude', function ($scope, $element, $transclude) {
$transclude(function(clone) {
//$transclude中接收的函数里的参数含有指令元素的内容(指令元素的内容就是指令内部的元素,也就是应该被transclude的内容,这里指的是[text, button.primary.ng-scope, text, button.primary.ng-scope, text, button.secondary.ng-scope, text])
//$element包含编译后的DOM元素(也就是把指令template进行了编译,这里指的是[div.span4.well.clearfix]),所以就可以在控制器中同时操作DOM元素和指令内容。
var primaryBlock = $element.find('div.primary-block');
var secondaryBlock = $element.find('div.secondary-block');
var transcludedButtons = clone.filter(':button');
angular.forEach(transcludedButtons, function(e) {
//每一个对象都有hasClass方法等
if (angular.element(e).hasClass('primary')) {
primaryBlock.append(e);
} else if (angular.element(e).hasClass('secondary')) {
secondaryBlock.append(e);
}
});
});
}],
};
});
最重要的是在我们的自定义指令中指定了一个controller函数:
controller: ['$scope', '$element', '$transclude', function ($scope, $element, $transclude) {
$transclude(function(clone) {
//$transclude中接收的函数里的参数含有指令元素的内容(指令元素的内容就是指令内部的元素,也就是应该被transclude的内容,这里指的是[text, button.primary.ng-scope, text, button.primary.ng-scope, text, button.secondary.ng-scope, text])
//$element包含编译后的DOM元素(也就是把指令template进行了编译,这里指的是[div.span4.well.clearfix]),所以就可以在控制器中同时操作DOM元素和指令内容。
var primaryBlock = $element.find('div.primary-block');
var secondaryBlock = $element.find('div.secondary-block');
var transcludedButtons = clone.filter(':button');
angular.forEach(transcludedButtons, function(e) {
//每一个对象都有hasClass方法等
if (angular.element(e).hasClass('primary')) {
primaryBlock.append(e);
} else if (angular.element(e).hasClass('secondary')) {
secondaryBlock.append(e);
}
});
});
}],
};
这时候我们在$transclude中传入的函数接受一个参数,这个参数代表我们需要通过transclude包含的内容,说白了也就是
我们的自定义指定内部的子元素集合!而$element就是我们指令的template编译后得到的DOM结构:我们看看这个接受到的clone的签名是怎么样的:
很显然就是我们在页面中自定义指令内部的元素进行编译后的DOM元素,有一点注意:这个编译后的DOM含有空格,也就是text元素类型。但是我们可能只是需要其中的button元素,于是在上面引入了filter方法,但是一直报错说"angular.js:62 TypeError: clone.filter is not a function",但是这个例子确实是可以运行的,所以这里我记录下来,以后慢慢理解。我们看看这时候ng为我们提供了那些方法,这些方法都是来源于jqLite的:
很显然如果我们需要对DOM进行操作,这些方法都是可以调用的。
<编译函数参数中的transclude>:
DOM结构和上面一样,我们看看指令内部的代码:
var testapp = angular.module('testapp', []);
//这里注入了$window对象
testapp.controller('parentController', ['$scope', '$window', function($scope, $window) {
$scope.primary1Label = 'Prime1';
$scope.onPrimary1Click = function() {
$window.alert('Primary 1 clicked');
}
}]);
testapp.directive('primary', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
element.addClass('btn btn-primary');
}
}
});
testapp.directive('secondary', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
element.addClass('btn');
}
}
});
//这里是我们的指令buttonBar
testapp.directive('buttonBar', function() {
return {
restrict: 'EA',
template: '',
replace: true,
transclude: true,
//为我们的编译函数参数中的transclude
compile: function(elem, attrs, transcludeFn) {
return function (scope, element, attrs) {
//这里我们指定了第一个参数为scope
transcludeFn(scope, function(clone) {
var primaryBlock = elem.find('div.primary-block');
var secondaryBlock = elem.find('div.secondary-block');
//这里的clone就是我们的应该被transclude的内容的DOM结构,当然其中也是包含空格符号的,所以需要过滤
console.log(clone);
var transcludedButtons = clone.filter(':button');
//对我们的button元素进行遍历
angular.forEach(transcludedButtons, function(e) {
if (angular.element(e).hasClass('primary')) {
primaryBlock.append(e);
} else if (angular.element(e).hasClass('secondary')) {
secondaryBlock.append(e);
}
});
});
};
}
};
});
其实这种方式是在我们自定义指令中使用了一个compile函数,并且在编译函数compile中指定了transclude参数:
compile: function(elem, attrs, transcludeFn) {
return function (scope, element, attrs) {
//这里我们指定了第一个参数为scope
transcludeFn(scope, function(clone) {
var primaryBlock = elem.find('div.primary-block');
var secondaryBlock = elem.find('div.secondary-block');
//这里的clone就是我们的应该被transclude的内容的DOM结构,当然其中也是包含空格符号的,所以需要过滤
var transcludedButtons = clone.filter(':button');
//对我们的button元素进行遍历
angular.forEach(transcludedButtons, function(e) {
if (angular.element(e).hasClass('primary')) {
primaryBlock.append(e);
} else if (angular.element(e).hasClass('secondary')) {
secondaryBlock.append(e);
}
});
});
};
}
我们为transclude函数传入了第一个参数为scope,同时第二个参数为一个函数,这个函数接受来自于自定义指令的内部元素,也就是需要被添加到ng-transclude指令中的元素。而外层函数compile接受第一个参数为elem,其代表的对template进行编译后的DOM树,第三个参数才是我们的transclude函数。阅读 angular中的transclude一文定能收获不少