一、组织控制器
1.1 使用一个单块控制器
1.2 复用控制器
你可以在同一个应用程序中创建多个视图并复用同一个控制器。AngularJS将会调用每个应用到控制器的工厂函数,结果是每个控制器实例将会拥有自己的作用域。
1)作用域之间的通信
作用域实际上是以层级结构的形式组织起来的,顶层是根作用域(rootScope),每个控制器都会被赋予一个新的作用域,该作用域是根作用域的一个子作用域。根作用域提供了在各个作用域之间发送事件的方法,这暗示着允许在各个控制器之间进行通信。
所有的作用域,包括$rootScope服务,定义了若干可用于发送和接收事件的方法。
$broadcast(name,args) 向当前作用域下的所有子作用域发送一个事件。参数是事件名称以及一个用于向事件提供额外数据的对象
$emit(name,args) 向当前作用域的父作用域发送一个事件,直至根作用域
$on(name,handler) 注册一个事件处理函数,该函数在特定的事件被当前作用域收到时将会被调用
$broadcast和$emit事件都是具有方向性的,它们沿着作用域的层级结构向上发送事件直至根作用域,或者向下发送直至每一个子作用域。
1.3 使用控制器继承
ng-controller指令可以被内嵌在HTML元素上,产生一种被称为控制器继承的效果。这是一种目的在于减少代码重复的特性,可以让你在一个父控制器中定义公用功能并在一个或多个子控制器中使用。
<body ng-controller="topLevelCtrl"> <div class="well"> <h4>Top Level Controller</h4> <div class="input-group"> <span class="input-group-btn"> <button class="btn btn-default" type="button" ng-click="reverseText()">Reverse</button> <button class="btn btn-default" type="button" ng-click="changeCase()">Case</button> </span> <input class="form-control" ng-model="dataValue"> </div> </div> <div class="well" ng-controller="firstChildCtrl"> <h4>First Child Controller</h4> <div class="input-group"> <span class="input-group-btn"> <button class="btn btn-default" type="button" ng-click="reverseText()">Reverse</button> <button class="btn btn-default" type="button" ng-click="changeCase()">Case</button> </span> <input class="form-control" ng-model="dataValue"> </div> </div> <div class="well" ng-controller="secondChildCtrl"> <h4>Second Child Controller</h4> <div class="input-group"> <span class="input-group-btn"> <button class="btn btn-default" type="button" ng-click="reverseText()">Reverse</button> <button class="btn btn-default" type="button" ng-click="changeCase()">Case</button> <button class="btn btn-default" type="button" ng-click="shiftFour()">Shift</button> </span> <input class="form-control" ng-model="dataValue"> </div> </div> </body>
var app = angular.module("exampleApp", []); app.controller("topLevelCtrl", function ($scope) { $scope.dataValue = "Hello, Adam"; $scope.reverseText = function () { $scope.dataValue = $scope.dataValue.split("").reverse().join(""); } $scope.changeCase = function () { var result = []; angular.forEach($scope.dataValue.split(""), function (char, index) { result.push(index % 2 == 1 ? char.toString().toUpperCase() : char.toString().toLowerCase()); }); $scope.dataValue = result.join(""); }; }); app.controller("firstChildCtrl", function ($scope) { $scope.changeCase = function () { $scope.dataValue = $scope.dataValue.toUpperCase(); }; }); app.controller("secondChildCtrl", function ($scope) { $scope.changeCase = function () { $scope.dataValue = $scope.dataValue.toLowerCase(); }; $scope.shiftFour = function () { var result = []; angular.forEach($scope.dataValue.split(""), function (char, index) { result.push(index < 4 ? char.toUpperCase() : char); }); $scope.dataValue = result.join(""); } });
当通过ng-controller指令将控制器嵌入另一个控制器中时,子控制器的作用域便继承了父控制器作用域中的数据和行为。本例中每个控制器都有自己独有的作用域,但是子控制器的作用域包含了其父作用域的数据值和行为。
当单击"reverse"按钮时,这些输入框元素都被链接到dataValue属性上,而且这些reverse按钮都调用reverseText行为,这两个属性和行为都是在顶层控制器中定义的。子控制器继承了数据值和行为,这也是为什么当单击任意一个reverse按钮时,所有的输入框值都会跟着改变,即使是在子控制器中实现的按钮。
1、扩展被继承的数据和行为
使用控制器继承的主要好处在于能够将从父控制器继承来的功能和自己定义的其他功能混合起来。在secondChildCtrl控制器中,定义了一个名为shiftFour的行为,该行为只在secondChildCtrl控制器的作用域中才是可用的.
2、覆盖被继承的数据和行为
子控制器能够覆盖他们的父控制器中的数据和行为,也就是说数据值和行为能够被同名的局部数据和行为所覆盖。当查找行为时,angularjs会从该指令所应用到的控制器的作用域上开始查找。如果该行为存在,就将会被执行。如果不存在,angularjs将会向作用域层级的上一层继续查找,直到具有指定名称的行为被找到。可以使用这一特性在大多数时候使用父控制器中所提供的功能,而只改写需要自定义的部分。这允许你创建为应用程序的不同部分量身定做的控制器,而无需从父控制器中拷贝代码和数据。
3、理解数据继承
如果修改关联到第二个子控制器的输入框元素中的内容。再次轮流点击所有reverse按钮,你会看到另外一种行为。所有三个按钮只对前两个输入框元素起作用,而被编辑修改过的那个输入框元素毫无变化。当读取一个直接在作用域上定义的属性的值时,angularjs会检查在这个控制器的作用域上是否有一个局部属性,如果没有,就会沿着作用域层次结构向上查找是否有一个被继承的属性。然而,当使用ng-model指令来修改这样一个属性时,angularjs会检查当前作用域是否有这样一个名称的属性,如果没有,就会假设你想隐式定义一个这样的属性。结果便是覆盖了该属性值,而出现上述现象,当编辑了子控制器中的输入框后,就会影响reverse按钮的工作,因为现在有两个dataValue属性,一个是被顶层控制器所定义的,一个是被编辑的子控制器所定义的。reverseText行为是在顶层控制器中定义的,只对顶层作用域中定义的dataValue属性起作用,而不会改变子作用域中的dataValue属性。
要想改变这个现象,可以将属性值dataValue的定义修改为对象,$scope.dataValue="Hello,Adam"==>$scope.data={dataValue:"Hello,Adam"}.如果在作用域上定义一个对象然后在对象上定义数据属性,这一切就不会发生,这是因为javascript对继承的实现是基于所谓的"原型继承"。
如果你想数据值在开始时是被共享的但在修改时会被复制一份,就直接在作用域上定义数据属性。如果想确保始终只有一份数据值,就通过一个对象来定义数据属性。
二、使用无作用域的控制器
<div class="well" ng-controller="simpleCtrl as ctrl"> <h4>Top Level Controller</h4> <div class="input-group"> <span class="input-group-btn"> <button class="btn btn-default" type="button" ng-click="ctrl.reverseText()">Reverse</button> </span> <input class="form-control" ng-model="ctrl.dataValue"> </div> </div>
var app = angular.module("exampleApp", []) .controller("simpleCtrl", function () { this.dataValue = "Hello, Adam"; this.reverseText = function () { this.dataValue = this.dataValue.split("").reverse().join(""); } });
当应用无作用域的控制器时,ng-controller指令的表达式格式会有所不同,需要指定一个代表控制器的变量名。<要应用的控制器>as<变量名>
三、显示更新作用域
作用域集成方法:
$apply(expression) 向作用域应用变化
$watch(expression,handler) 注册一个处理函数,当expression表达式所引用的值变化时,该函数会被通知到
$watchCollection(object,handler) 注册一个处理函数,当指定的object对象的任一属性变化时,该函数就会被通知到
$apply方法提供了对内集成的手段,这样在其他框架中的变化就可以引起在angularjs中的相应变化。angularjs提供了angular.element方法,传递所关心元素的id属性值给这个方法,就可以得到一个定义了scope方法的对象,并返回所需的作用域。
$(document).ready(function () { $('#jqui button').button().click(function (e) { angular.element(angularRegion).scope().$apply('handleClick()'); }); });