angularjs中的transclude参数的理解($tranclude服务以及compile中transclude)

首先我们来和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所在的位置。
DOM结构如下:

这就比较容易理解了,这里的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一文定能收获不少

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值