《学习笔记》AngularJS中把作用域实现为数据模型

    AngularJS应用程序最重要的方面之一是作用域。作用域不仅提供了在一个模型中表示的数据,而且还把AngularJS应用程序的所有其他组件,比如模块,控制器,服务和模板都绑定在一起。本篇文章介绍作用域与其它AngularJS组件之间的关系。

    作用域绑定了绑定机制,这允许当模型中的数据发生变化时,对DOM元素和其他代码进行更新。在下面的文章中,你将学习作用域的层次以及如何通过事件在这些层次之间进行通信。

1,了解作用域

    在AngularJS中,作用域充当一个应用程序的数据模型。对于依赖于任何方式的任何应用程序,作用域都是最重要的部分之一,因为它充当了把视图,业务逻辑和服务器端数据结合在一起的胶黏剂。了解作用域如何工作,你就可以把自己的AngularJS应用程序设计得更有效率,使用的代码更精炼,并且更容易理解。

    以下各节讨论作用域与应用程序,控制器,模板及服务器端数据之间的关系。还有一节专门介绍作用域的生命周期,以帮助你了解在应用程序生命周期中,作用域是如何创建,操作和更新的。

1.1,根作用域和应用程序之间的关系

    当一个应用程序启动时,根作用域就创建了。根作用域将数据存储在应用层,你可以使用$rootScope服务来访问它。根作用域中的数据应该在模块的run()块中进行初始化,但你也可以在模块组件中访问它。为了说明这一点,下面的代码在根作用域层次定义一个值,然后在一个控制器中访问它:

angular.module('myApp',[])
.run(function($rootScope){
	$rootScope.rootValue = 5;
})
.controller('myController',function($scope,$rootScope){
	$scope.value = 10;
	$scope.difference = function(){
		return $rootScope.rootValue - $scope.value;
	};
});
1.2,根作用域和控制器之间的关系

    控制器是旨在通过扩大作用域来提供业务逻辑的代码段。控制器使用应用程序的model对象上的controller()方法创建。这个函数把一个控制器注册为模板中的提供器,但它并没有创建该控制器的一个实例。实例的创建发生在ng-controller指令被链接到AngularJS模板中时。

    controller()方法接受控制器名称作为第一个参数,并以依赖的数组作为第二个参数。例如,下面的代码定义了一个控制器,它使用依赖注入来访问名为start的值提供器:

angular.module('myApp',[]).
	value('start',200).
	controller('Counter',['$scope','start',function($scope,startingValue){
	}]);

    当控制器的一个新实例在AngularJS中创建时,特定于该控制器的一个新子作用域也被创建并可通过上面注入到Counter控制器的$scope服务来访问。此外,在上面的例子中,start提供器被注入到控制器,并作为startingValue被传递到该控制器函数。

    该控制器必须初始化被创建并被添加到它的作用域的状态。该控制器还负责链接到该作用域中的任何业务逻辑。这意味着处理对作用域的更新更改,操作作用域值,或发出基于该作用域的状态的事件。

    下面的代码清单实现了一个利用依赖注入的控制器。实现使用依赖注入的基本控制器,初始化作用域的值,并实现业务逻辑

angular.module('myApp',[]).
	value('start',200).
	controller('Counter',['$scope','start',function($scope,start){
		$scope.start = start;
		$scope.current = start;
		$scope.difference = 0;
		$scope.change = 1;
		$scope.inc = function(){
			$scope.current += $scope.change;
			$scope.calcDiff();
		};
		$scope.dec = function(){
			$scope.current -= $scope.change;
			$scope.calcDiff();
		};
		$scope.calcDiff = function(){
			$scope.difference = $scope.current - $scope.start;
		}
	}]);
	
1.3,作用域和模板之间的关系

    模板为AngularJS应用程序提供视图。HTML元素使用ng-controller属性别定义为控制器。在控制器的HTML元素及其子元素内部,该控制器的作用域可用于表达式和其他AngularJS功能。

    在一个作用域中的值可以利用ng-model指令直接链接到模板的<input>,<select>和<textarea>元素的值中。指令把元素的值链接到作用的属性名称中。当用户改变输入元素的值时,该作用域被自动更新。例如,下面的代码把<input>元素的一个数值链接到命名为valueA的作用域中:

<input type="number" ng-model="valueA"/>

    你可以在模板中使用{{expression}}语法来把作用域的属性,甚至函数添加到表达式。括号内的代码被求值,而结果显示在呈现的视图中。例如,如果作用域包含名为valueA和valueB的属性,则可以在模板中的表达式里引用这些属性,如下所示:

{{valueA+valueB}}

    你也可以在定义模板中的AngualrJS指令是使用作用域属性和函数。例如,下面的ng-click指令把浏览器单击事件绑定到作用域中名为addValue()的函数上,并传递作用域内的属性valueA和valueB的值:

<span ng-click="addValues(valueA,valueB)">Add values{{valueA}}&{{valueB}}</span>

    请注意,在这段代码中,{{}}括号是必需的。然而,在addValues()函数调用中,它们不是必须的。那是因为ng-click和其他AngularJS指令会自动对表达式求值。

下面的代码清单用一个非常简单的例子将所有这些概念结合起来:

                scope_template.js:实现基本的控制器来支持模板功能

angular.module('myApp',[]).
	controller('SimpleTemplate',function($scope){
		$scope.valueA = 5;
		$scope.valueB = 7;
		$scope.valueC = 12;
		$scope.addValues = function(v1,v2){
			var v = angular.$rootScope;
			$scope.valueC = v1 + v2;
			$scope.valueA = $scope.valueC;
		};
	});

                        scope_template.html:HTML模板代码,实现了一个控制器和各个链接到作用域的HTML字段

<!doctype html>
<html>
<head>
	<title>AngularJS Dependency Injection</title>
</head>
	<script src="js/angular.js"></script>
<body ng-app="myApp">
	<div ng-controller="SimpleTemplate">
		ValueA:<input type="number" ng-model="valueA" /><br>
		ValueB:<input type="number" ng-model="valueB" /><br><br>
		Expression Value:{{valueA +valueB}}<br><br>
		<input type="button" ng-click="addValues(valueA,valueB)"
			value = "Click to Add Values{{valueA}}&{{valueB}}" / ><br>
		Click Value: {{valueC}}<br>
	</div>
	<script src="js/angular.js"></script>
	<script src="js/scope_template.js"></script>
</body>
</html>

显示结果:


1.4,作用域和后端服务器数据之间的关系

    用于AngularJS应用程序的数据往往来自一个后端数据源(例如数据库)。在这种情况下,作用域仍充当于AngularJS应用程序的数据的权威性来源。与来自服务器端的数据进行交互时,你应该采用下面的规则:

  • 经由AngularJS服务来访问来自数据库或其他后端资源的数据,
  • 确保从服务器读出的数据对作用域进行更新,而这又更新视图。需避免直接从数据库中操作HTML值,这种操作可能会导致作用域和视图变得不同步。
  • 把对数据库或其他后端源所做的更改反映到作用域。你可以通过先更新作用域,然后使用一个服务对数据库进行更新来指向此操作;也可以先更新数据库,然后使用来自数据库的结果在作用域内重新填充适当的值。
1.5,作用域的生命周期

    当应用程序在浏览器中加载时,作用域的数据经过了一个生命周期。理解这个生命周期将帮你了解作用域与其它AngularJS组件,尤其是它与模板之间的相互作用。

    作用域的数据经过以下生命周期阶段:

  1. 创建
  2. 监视器注册
  3. 模型变化
  4. 变化观察
  5. 作用域销毁

    这些生命周期将在下面各节中描述

    创建阶段

    当一个作用域初始化时,创建阶段产生。启动应用程序会创建根作用域。当遇到ng-controller或ng-repeat指令时,则链接创建子作用域的模板。

    另外,在创建阶段,创建一个digest循环来与浏览器的事件循环互动。digest循环负责把对模型的更改更新到DOM元素,并且执行任何已注册的监视函数。虽然你不需要手动执行digest循环,但你可以通过在作用域执行$digest()方法这样做。例如,下面的代码计算任何的异步变化,然后执行作用域上的监视函数:

$scope.$digest()
    监视器注册阶段

    模板链接阶段为在模板中表示的作用域中的值注册监视器。这些监视器自动将模型的更改传播到DOM元素。

    你也可以利用$watch()方法在一个作用域上注册你自己的监视函数。这个方法接受一个作用域名称作为第一个参数,然后用一个回调函数作为第二个参数。当该属性在作用域中发生变化时,旧值和新值都被传递给回调函数。

    例如,下面的代码为作用域中的watchedItem属性添加一个监视器,并在它每次更改时递增计数器的值:

$scope.watchedItem = 'myItem';
$scope.counter = 0;
$scope.$watch('name',function(newValue,oldValue){
	$scope.watchItem = $scope.counter + 1;
	});
    模型变化阶段

    模型变化阶段发生在该作用域内的数据变化时。当你在AngularJS代码进行更改时,一个名为$apply()的作用域函数更新模型,并调用$digest()函数来更新DOM和监视器。这就是在你的AngularJS控制器中做出的更改,或由$http,$timeout和$interval服务所做的更改自动在DOM中更新的方法。

    你应该始终在AngualrJS控制器或这些服务里面尝试更改作用域。但是如果你必须更改AngularJS领域之外的作用域,就需要在作用域中调用scope.$apply(),迫使模型和DOM被正确地更新。$apply()方法接受一个表达式作为唯一的参数。该表达式被求值并被返回,而$digest()方法被调用来更新DOM和监视器。

    变化观察阶段

    当$digest()方法被digest循环,$apply()调用或手动执行时,变化观察阶段发生。当$digest()执行时,它会对所有用于更改的监视器求值。如果值发生了变化,$digest()就调用$watch监听器并更新DOM。

    作用域销毁阶段

    $destory()方法从浏览器内存中删除作用域。在子作用域不在需要时,AngualrJS库自动调用此方法。$destory()方法停止$digest()调用并删除监视器,允许内存被浏览器垃圾收集器回收。

2,实现作用域层次结构

    作用域的一个很棒的特点是,它们被组织成一个层次结构。层次结构可以帮组你保持作用域有条理,并将它们与所代表的视图的上下文联系起来。此外,$digest()方法使用作用域层次来为适当的监视器和DOM元素传播作用域的变更。

    作用域层次结构是基于AngularJS模板中ng-controller语句的位置自动创建的。例如,下面的模板代码定义了两个<div>元素,它们创建兄弟关系的控制器的实例:

<div ng-controller="controllerA">...</div>
<div ng-controller="controllerB">...</div>

    但是,在下面的模板代码定义的控制器中,其中controllerA是controllerB的父:

<div ng-controller="controllerA">
	<div ng-controller="controllerB">
	</div>
</div>

    你可以从一个控制器访问父作用域的值,但不能访问它的兄弟或子作用域的值。如果你在一个子作用域添加一个属性名称,它不会覆盖父属性,而是在子作用域创建不同于父作用域的值的同名属性。

    下面的代码清单实现了一个基本的作用域层次结构来说明作用域是如何在一个层次中工作的。

                scope_hiercarchy.js:实现一个基本的作用域,它访问层次结构每个级别的属性

angular.module('myApp',[]).
	controller('LevelA',function($scope){
		$scope.title = "Level A";
		$scope.valueA = 1;
		$scope.inc = function(){
			$scope.valueA++;
		};
	}).
	controller('LevelB',function($scope){
		$scope.title = "Level B";
		$scope.valueB = 1;
		$scope.inc = function(){
			$scope.valueB++;
		};
	}).
	controller('LevelC',function($scope){
		$scope.title = "Level C"
		$scope.valueC = 1;
		$scope.inc = function(){
			$scope.valueC++;
		};
	});

                scope_hierarchy.html:HTML模板代码,实现控制器的层次结构并呈现作用域的多个层次的结果

<!doctype html>
<html>
<head>
	<title>AngularJS Dependency Injection</title>
</head>
	<script src="js/angular.js"></script>
	<script src="js/scope_hierarchy.js"></script>
<body ng-app="myApp">
	<div ng-controller="LevelA">
	<h3>{{title}}</h3>
	valueA = {{valueA}}<input type="button" ng-click="inc()" value="+" />
	<div ng-controller="LevelB">
	<h3>{{title}}</h3>
	valueA = {{valueA}}
	valueB = {{valueB}}<input type="button" ng-click="inc()" value="+" />
	<div ng-controller="LevelC">
	<h3>{{title}}</h3>
	valueA = {{valueA}}
	valueB = {{valueB}}
	valueC = {{valueC}}<input type="button" ng-click="inc()" value="+" />
	</div>
	</div>
	</div>
</body>
</html>

3,发出和广播活动

    作用域的一个很棒的特点是具有在作用域层次结构内发出和广播事件的能力。事件允许你发送通知给该作用域不同的层次,告诉它们事件已经发生。事件可以是你选择的任何东西,例如值改变或达到阈值。这在很多情况下非常有用,如让子作用域知道一个值已在父作用域中发生变化,反之亦然。

    要从作用域发出一个事件,你可以使用$emit()方法。该方法沿着父作用域层次向上发送一个事件。任何已注册该事件的祖先作用域都会受到通知。$emit()方法使用下面的语法,其中name是事件名称,而args是传递给事件处理函数的零个或多个参数:

scope.$emit(name,[args,...])

    你可以利用$broadcast()方法把一个事件广播给下方的子作用域层次。任何已注册该事件的后代作用域都会收到通知。$broadcast()方法使用下面的语法,其中name是事件名称,而args是传递给事件处理函数的零个或多个参数:

scope.$broadcast(name,[args,...])

    要处理发出或广播的事件,你可以使用$on()方法。$on()方法使用下面的语法,其中name是要监听的事件名称:

scope.$on(name,listener)

    listener参数是一个函数,它可以接受事件作为第一个参数,并把由$emit()或$broadcast()方法传递的任何参数作为后续的参数。event对象具有以下属性。

  • targetScope:$emit()或$broadcast()被调用时所在的作用域。
  • currentScope:当前正在处理该事件的作用域
  • name:事件的名称
  • stopPropagation():停止在作用域层次结构中向上或向下传播事件的函数。
  • preventDefault():防止在浏览器的事件中的默认行为,而只执行自己的自定义代码的函数。
  • defaultPrevented:一个布尔值,如果event.preventDefault()被调用,则为true

   下面的代码清单演示了$emit(),$broadcast()和$on()来在作用域的层次结构中上下发送和处理事件。

            scope_events.js:在作用域层次结构中实现$emit()和$broadcast()事件

angular.module("myApp",[]).
	controller('Characters',function($scope){
		$scope.names = ['Frodo','Aragorn','Legolas','Gimli'];
		$scope.currentName = $scope.names[0];
		$scope.changeName = function(){
			$scope.currentName = this.name;
			$scope.$broadcast('CharacterChanged',this.name);
		};
		$scope.$on('CharacterDeleted',function(event,removeName){
			var i = $scope.names.indexOf(removeName);
			$scope.names/splice(i,1);
			$scope.currentName = $scope.names[0];
			$scope.$broadcast('CharacterChanged',$scope.currentName);
		});
	}).
	controller('Character',function($scope){
		$scope.info = {'Frodo':{weapon:'String',race:'Hobbit'},
					   'Aragorn':{weapon:'Sword',race:'Man'},
					   'Legolas':{weapon:'Bow',race:'Elf'},
						'Gimli':{weapon:'Axe',race:'Dwarf'}};
		$scope.currentInfo = $scope.info['Frodo'];
		$scope.$on('CharacterChanged',function(event,newCharacter){
			$scope.currentInfo = $scope.info[newCharacter];
		});
		$scope.deleteChar = function(){
			delete $scope.info[$scope.currentName];
			$scope.$emit('CharacterDeleted',$scope.currentName);
		};
	});

        scope_events.html:HTML模板代码,

<!doctype html>
<html ng-app="myApp">
<head>
	<style>
		div{padding:5px;font:18px bold;}
		span{padding:3px;margin:12px;border:5px ridge;
			cursor:pointer;}
		label{padding:2px;margin:5px;font:15px bold;}
		p{padding-left:22px;margin:5px;}
	</style>
</head>
<body>
	<div ng-controller="Characters">
		<span ng-repeat="name in names" ng-click="changeName()">{{name}}</span>
	<div ng-controller="Character"><hr>
		<label>Name:</label><p>{{currentName}}</p>
		<label>Race:</label><p>{{currentInfo.race}}</p>
		<label>Weapon:</label><p>{{currentInfo.weapon}}</p>
		<span ng-click="deleteChar()">Delete</span>
	</div>
	</div>
	<script src="js/angular.js"></script>
	<script src="js/scope_events.js"></script>
</body>
</html>

4,总结

    作用域是AngularJS应用程序中数据的权威来源。作用域与模板视图,控制器,模块和服务有直接关系,并作为把应用陈序结合在一起的胶黏剂。作用域也作为数据库或其他服务器端数据源的代表。

    作用域的生命周期被链接到浏览器的事件循环中,以便在浏览器中的变化可以该表作用域,而作用域的变化反映在它的DOM元素上。你可以添加当作用域变更时接受通知的自定义监视器函数。

    作用域被组织成层次结构,而跟作用域定义在应用程序的层次上。控制器的每个实例也得到了一个子作用域的实例。你可以从作用域内发出或广播事件,然后监听这些事件并在它们被发送时执行的处理程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值