Benjamin - 专注前端开发和用户体验

NGJS-7 指令(Directives)-3

AngularJS教程 Benjamin 2335℃ 0评论

在本文的开始想先大致描述下指令的编译过程,之后将重点描述指令中最为重要的scope及其相关用法。

十五、指令的编译过程

看过源码的都知道当DOM加载完成后,Angular应用开始初始化,Angular开始使用编译器的$compile服务遍历DOM元素。整个编译过程分为两个阶段:

编译阶段:
遍历DOM并且收集所有相关指令,当所有的指令被识别后,Angular执行它们的compile方法,该方法返回一个link函数,被添加到稍后linking阶段执行的link函数列表中。

linking阶段:
在这个阶段,所有link列表中的link函数将被一一执行。指令创造出来的模板会在正确的scope下被解析和处理,然后返回具有事件响应的真实的DOM节点。

需要注意的是:
如果一个指令需要被克隆很多次(像ng-repeat),compile函数只在编译阶段被执行一次,复制这些模板,但是link 函数会针对每个被复制的实例执行一次。所以分开处理,让我们在性能上有一定的提高。这也说明了为什么在 compile 函数中不能访问到scope对象的原因。

十六、配置属性scope

16.1 scope:false(默认值)

默认情况下,指令不会创建新的scope,指令获取它父节点的controller的scope。此时,scope可以访问父节点controller中的属性和方法。但需要注意的是,如果将父controller的scope暴露给指令,那么他们可以随意地修改scope的属性。在某些情况下(比如组件化开发),你的指令希望能够添加一些仅限内部使用的属性和方法。如果我们在父的scope中添加,会污染父scope。这是不允许的,同时会给我的开发和维护带来很大困难。
实例:
参考NGJS-6 指令(Directives)-2一节实例二,不再赘述。

16.2 scope: true

如果设置为true,指令会创建新的scope,如果同一元素上包含的多个指令都需要一个新的scope,只有一个新的scope会被创建。这个新的scope规则不适用于模板的根元素,因为模板的根元素总会获得一个新的scope。
实例:

<div ng-app="myApp">
	<div ng-controller="FirstController">
		<div ng-self-text></div>
	</div>
</div>
<script type="text/javascript">
	angular.module('myApp', [])
	.controller('FirstController', function($scope) {
		$scope.users = [
			{name: "zhangsan", sex: "male"},
			{name: "lisi", sex: "female"},
			{name: "wanger", sex: "male"},
			{name: "zaowu", sex: "female"}
		]
	})
	.directive('ngSelfText', function() {
		return {
			scope: true,
			template: 'ng-self-text',
			link: function(scope, tElem, tAttrs) {
				console.log(scope.users);
			}
		}
	})
</script>

Chrome控制台下查看Elements:

<div ng-app="myApp" class="ng-scope">
	<div ng-controller="FirstController" class="ng-scope">
		<div ng-self-text="" class="ng-scope">ng-self-text</div>
	</div>
</div>

 

此时在div[ng-self-text]元素上创建了一个ng-scope,该scope原型继承于父scope,因此该scope可以访问父scope的属性和方法。该scope中定义的属性和方法不会污染父scope。

16.3 scope: {}

如果设置为{}(hash对象),这时会创建一个”isolate”scope(隔离scope),隔离作用域和正常的作用域不同,它不会继承于它的父scope,当我们创建可重用组件时隔离scope是非常有好处的,通过使用隔离的scope,我们能够保证我们的指令是自包含的,这样我们读取和修改数据就不会影响父scope。

隔离指令的scope会带来很多的便利,尤其是在你要操作多个scope模型的时候。但有时为了使代码能够正确工作,你也需要从指令内部访问父scope的属性。庆幸的是Angular给了你足够的灵活性让你能够有选择性的通过绑定的方式传入父scope的属性。下面我们来看看这几种实现方式:

1. @ or @attr:
通过@或者@attr,可以绑定一个隔离scope属性到DOM属性上。因为DOM属性值是字符串,所以结果总是一个字符串。如果没有指定attr名称,假定对应的元素属性名称与隔离scope属性相同。
实例一:@attr实现单向文本绑定

<div ng-app="myApp">
	<div ng-controller="FirstController">
		<div ng-self-text color="my-color"></div>
	</div>
</div>
<script type="text/javascript">
	angular.module('myApp', [])
	.controller('FirstController', function($scope) {

	})
	.directive('ngSelfText', function() {
		return {
			scope: {
				localColor: "@color"
			},
			template: 'ng-self-text',
			link: function(scope, tElem, tAttrs) {
				//Outputs: my-color 
				console.log(scope.localColor);
			}
		}
	})
</script>

实例二:@实现单向文本绑定

<div ng-app="myApp">
	<div ng-controller="FirstController">
		<div ng-self-text color="my-color"></div>
	</div>
</div>
<script type="text/javascript">
	angular.module('myApp', [])
	.controller('FirstController', function($scope) {

	})
	.directive('ngSelfText', function() {
		return {
			scope: {
				color: "@"
			},
			template: 'ng-self-text',
			link: function(scope, tElem, tAttrs) {
				//Outputs: my-color 
				console.log(scope.color);
			}
		}
	})
</script>

使用@时,默认隔离scope的属性名称color与div[ng-self-text]元素的属性名称相同。

2. = or =attr:
使用=或者=attr可以在隔离scope与父scope属性名(attr的值)间实现双向绑定。如果没有指定attr值,假定attr的值与隔离scope的属性名称相同。

如果父scope属性不存在,会抛出一个NON_ASSIGNABLE_MODEL_EXPRESSION异常。你可以使用=?或者=?attr使属性可选,以避免异常。另,If
you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
`=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).

实例一:=attr实现双向绑定

<div ng-app="myApp" class="demo">
	<div ng-controller="FirstController">
		<input type="text" ng-model="bgcolor">
		<div ng-self-text color-attr="bgcolor"></div>
	</div>
</div>
<script type="text/javascript">
	angular.module('myApp', [])
	.controller('FirstController', function($scope) {
		$scope.bgcolor = "#ccc";
	})
	.directive('ngSelfText', function() {
		return {
			scope: {
				localColor: "=colorAttr"
			},
			template: '<span style="background-color: {{scopeBgcolor}}">background-color: {{scopeBgcolor}}</span>',
			link: function(scope, tElem, tAttrs) {
				scope.scopeBgcolor = scope.localColor;

				tElem
				.css("cursor", "pointer")
				.bind('mouseenter', function() {
					scope.$apply(function() {
						scope.scopeBgcolor = "#f0f";
						scope.localColor   = "#f0f";
					})
				})
				.bind('mouseleave', function() {
					scope.$apply(function() {
						scope.scopeBgcolor = "#ccc";
						scope.localColor   = "#ccc";
					})
				})					
			}
		}
	})
</script>

Chrome控制台下查看Elements:

<div ng-app="myApp" class="demo ng-scope">
	<div ng-controller="FirstController" class="ng-scope">
		<input type="text" ng-model="bgcolor" class="ng-valid ng-dirty ng-valid-parse ng-touched">
		<div ng-self-text="" color-attr="bgcolor" class="ng-isolate-scope" style="cursor: pointer;">
			<span style="background-color: #ccc" class="ng-binding">background-color: #ccc</span>
		</div>
	</div>
</div>

div[ng-self-text]元素增加了一个ng-isolate-scope。如果想查看实例请戳这里

实例二:=实现双向绑定(父scope、隔离scope、model)

<div ng-app="myApp" class="demo">
	<div ng-controller="FirstController">
		<input type="text" ng-model="bgcolor">
		<div ng-self-text bgcolor="bgcolor"></div>
	</div>
</div>
<script type="text/javascript">
	angular.module('myApp', [])
	.controller('FirstController', function($scope) {
		$scope.bgcolor = "#ccc";
	})
	.directive('ngSelfText', function() {
		return {
			scope: {
				bgcolor: "="
			},
			template: '<span style="background-color: {{bgcolor}}">background-color: {{bgcolor}}</span>',
			link: function(scope, tElem, tAttrs) {
				tElem
				.css("cursor", "pointer")
				.bind('mouseenter', function() {
					scope.$apply(function() {
						scope.bgcolor = "#f0f";
					})
				})
				.bind('mouseleave', function() {
					scope.$apply(function() {
						scope.bgcolor = "#ccc";
					})
				})					
			}
		}
	})
</script>

使用=实现双向绑定时,隔离scope属性名与元素属性名、ng-model值相同时,会发现数据模型到指令模板可以实现联动。我可以试想一下,如果我想实现搜索联动、二级三级联动时,这样是不是就很方便了~~~。如果想查看实例请戳这里

与 @ 不同,这种方式让你能够给属性指定一个真实的scope数据模型,而不是简单的字符串。这样你就可以传递简单的字符串、数组、甚至复杂的对象给隔离scope。同时,还支持双向的绑定。每当父scope属性变化时,相对应的隔离scope中的属性也跟着改变,反之亦然。

3. & or &attr:
使用&或者&attr,提供了一种方式可以执行在父scope的上下文中定义的表达式和函数。如果没有指定attr值,假定attr的值与隔离scope的属性名称相同。

实例:

<div ng-app="myApp" class="demo">
	<div ng-controller="FirstController">
		<input type="button" value="Open  Dialog" ng-click="onOpen()">
		<div ng-show="showDialog" class="">
			<dialog on-close="onClose()"></dialog>
		</div>
	</div>
</div>
<script type="text/javascript">
	angular.module('myApp', [])
	.controller('FirstController', function($scope) {
		$scope.showDialog = false;
		$scope.onOpen  = function() {
			$scope.showDialog = true;
		}
		$scope.onClose = function() {
			if(confirm("是否关闭弹窗?")) {
				$scope.showDialog = false;
			}
		}
	})
	.directive('dialog', function() {
		return {
			restrict: 'E',
			scope: {
				onClose: "&"
			},
			template: '<div class="dialog">'
				+'<div class="dialog-title">'
					+'<div>Title<span class="dialog-close" ng-click="onClose()">Close</span></div>'
				+'</div>'
				+'<div class="dialog-content">弹窗正文</div>'
			+'</div>',

			link: function(scope, tElem, tAttrs) {
				
			}
		}
	})
</script>

如果想查看实例demo请戳这里

以上就是本节内容,就如果你对文中所述有疑问,欢迎给我留言,到此指令的常见配置属性就讲完了。下一节使用指令创建一个可复用的弹窗(dialog)组件。

更多阅读:
Develop ->Developer Guide -> HTML Compiler
Develop ->Developer Guide -> Directives

喜欢 (1)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址