首先我们来和replace进行一个对比:
<!doctype html> <html ng-app="MyModule"> <head> <meta charset="utf-8"> </head> <body> <hello> <div>这里是指令内部的内容。</div> </hello> </body> <script src="framework/angular-1.3.0.14/angular.js"></script> <script src="replace.js"></script> </html>我们看看replace中的代码:
var myModule = angular.module("MyModule", []); myModule.directive("hello", function() { return { restrict:"AE", template:"<div>Hello everyone!</div>", replace:true } });这时候我们很清楚的看到下面的DOM结构:
也就是说我们的template内容把<hello></hello>标签内部的div元素移除了,这有时候不是我们希望看到的结果,因为这种方式完全无法完成标签的嵌套!于是才引入了我们的transclude属性。
我们使用transclude来完成标签的嵌套工作:
<!doctype html> <html ng-app="MyModule"> <head> <meta charset="utf-8"> </head> <body> <hello> <div>这里是指令内部的内容。</div> </hello> </body> <script src="framework/angular-1.3.0.14/angular.js"></script> <script src="transclude.js"></script> </html>我们看看transcude在内部是如何完成的:
var myModule = angular.module("MyModule", []); myModule.directive("hello", function() { return { restrict:"AE", transclude:true, template:"<div>Hello everyone!<div ng-transclude></div></div>" } });很显然首先需要把transclude设置为true,然后在template中进行修改, 其中<div ng-transclude></div>就是告诉ng把hello标签内部的内容全部放在ng-transclude所在的位置。
这就比较容易理解了,这里的hello标签没有被替换掉,因为没有指定replace:true,至于内部<div ng-transclude></div>还是会原封不动的保存,只是内部原来的DOM结构被这个ng-transclude指定的div包裹起来了!而且内部原来的DOM也被添加了一个内置的class,也就是ng-scope,这一点一定要注意!
我们在最后给出一个综合的例子:
<html ng-app='expanderModule'> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <link rel="stylesheet" type="text/css" href="ExpanderSimple.css"/> <script src="framework/angular-1.3.0.14/angular.js"></script> <script src="ExpanderSimple.js"></script> </head> <body> <!--指定一个controller为SomeController--> <div ng-controller='SomeController'> <!--自定义指令expander--> <expander class='expander' expander-title='title'> {{text}} </expander> </div> </body> </html>我们看看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 : '<div>' + '<div class="title" ng-click="toggle()">{{title}}</div>' + '<div class="body" ng-show="showMe" ng-transclude></div>' + '</div>', //这里指定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服务>
<div ng-controller="parentController"> <button-bar> <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button> <button class="primary">Primary2</button> <button class="secondary">Secondary1</button> </button-bar> </div>我们看看内部是如何注入$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: '<div class="span4 well clearfix"><div class="primary-block pull-right"></div><div class="secondary-block"></div></div>', 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: '<div class="span4 well clearfix"><div class="primary-block pull-right"></div><div class="secondary-block"></div></div>', 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一文定能收获不少