在Angular中,控制器是由JavaScript构造函数定义的,参数是域(scope).
当一个控制器通过ng-controller
指令附加到DOM元素时,Angular将会实例化一个新的控制器对象,使用指定的控制器构造函数.一个新的子作用域会被创建并成为一个可注入的参数,名称为$scope
.
如果控制器通过使用controller as
语法附加到DOM元素时,控制器的实例将被复制到新作用域的一个属性上.
使用控制器:
- 设置
$scope
对象的初始状态 - 为
$scope
对象增加行为
不要使用控制器:
- 操作DOM--控制器应该只包含业务逻辑.增加任何显示逻辑到控制器上会显著影响控制器的可测试性.Angular拥有数据绑定和指令去封装DOM操作.
- 格式化输入--应该使用Angular表单控制.
- 过滤输出--使用过滤器代替.
- 通过控制器共享代码和状态--使用Angular的自定义服务代替.
- 管理其他组件的生命周期(例如创建服务的实例)
当你创建一个应用程序时,你需要为你的$scope
对象设置初始状态.你可以通过给$scope
添加新的属性去设置初始状态.这些属性包含了view model(模型会通过视图显示出来).所有的$scope
属性可以在控制器注册的DOM元素和子元素上使用.
接下来的例子展示了创建一个GreetingController
控制器,并添加了一个值为Hola!
的greeting
属性到$scope
:
var myApp = angular.module('myApp',[]);
myApp.controller('GreetingController', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
}]);
我们为我们的程序创建了一个Angular模块myApp
.然后我们使用.controller()
方法将控制器的构造函数添加到模块上.使得控制器的构造函数没有暴露在全局作用域.
我们使用内联的数组注解去显示的指定控制器对由Angular提供的
$scope
服务的依赖.查看依赖注入章节获得更多的信息.
我们通过使用ng-controller
指令将控制器附加到DOM元素上.greeting
属性能被数据绑定到模板上.
<div ng-controller="GreetingController">
{{ greeting }}
</div>
为了响应时间或者在视图中执行计算,我们必须为$scope
提供行为.我们可以通过给$scope
对象添加方法的方式去为scope添加行为.这些方法可以在视图/模板中调用.
接下来的例子使用了一个控制器去添加方法,使数字翻倍:
var myApp = angular.module('myApp',[]);
myApp.controller('DoubleController', ['$scope', function($scope) {
$scope.double = function(value) { return value * 2; };
}]);
一旦控制器被附加到DOM元素上,这个double
函数可以在模板中以Angular表达式的方式执行:
<div ng-controller="DoubleController">
Two times <input ng-model="num"> equals {{ double(num) }}
</div>
像在概念那一章讨论的一样,任何对象或基本数据类型可以成为scope
的模型属性.任何scope
上的方法可以在视图/模板上使用,并且可以通过angular表达式和ng
事件处理器指令执行(如ng-click
)
通常来说,一个控制器不应该去处理过多的逻辑.它应该只包含单一视图的必要业务逻辑.
最普遍的方式去保持控制器的规模小,是通过封装不属于控制器的工作到服务中,然后在控制器中通过依赖注入去使用这些服务.
一可以隐式的关联控制器和scope对象通过ng-controller
指令或者$route
服务.
为了阐述控制器组件如何在Angular运作,我们创建一个拥有以下组件的小应用:
- 一个带有两个按钮和一个简单消息的模板
- 一个由名称为
spice
的字符串组成的模型 - 一个有两个为
spice
设置值的函数的控制器
在模版中的消息包含了一个spice
的绑定.默认的会被设置为"very"
.spice
模型会被设置为chili
或者jalapeño
取决于哪个按钮被按下,并且这个消息会通过数据绑定自动更新.
index.html
<div ng-controller="SpicyController">
<button ng-click="chiliSpicy()">Chili</button>
<button ng-click="jalapenoSpicy()">Jalapeño</button>
<p>The food is {{spice}} spicy!</p>
</div>
app.js
var myApp = angular.module('spicyApp1', []);
myApp.controller('SpicyController', ['$scope', function($scope) {
$scope.spice = 'very';
$scope.chiliSpicy = function() {
$scope.spice = 'chili';
};
$scope.jalapenoSpicy = function() {
$scope.spice = 'jalapeño';
};
}]);
在上面的例子中要注意的事项:
ng-controller
指令被用于为模板隐式地创建一个scope,并且这个scope被SpicyController
控制器管理.SpicyController
只是一个简单的Javascript函数.一个可选的命名规范,首字母大写切实用Controller
作为后缀.- 通过分配一个属性给
$scope
去创建或更新模型 - 控制器方法可以通过直接给scope赋值的方式创建
- 控制器方法和属性可以在模版中使用(包括
<div>
元素及其子元素)
##辣参数示例
控制器方法也可以拥有参数,下面的例子与之前的有一些变化 index.html
<div ng-controller="SpicyController">
<input ng-model="customSpice">
<button ng-click="spicy('chili')">Chili</button>
<button ng-click="spicy(customSpice)">Custom spice</button>
<p>The food is {{spice}} spicy!</p>
</div>
app.js
var myApp = angular.module('spicyApp2', []);
myApp.controller('SpicyController', ['$scope', function($scope) {
$scope.customSpice = 'wasabi';
$scope.spice = 'very';
$scope.spicy = function(spice) {
$scope.spice = spice;
};
}]);
注意SpicyController
控制器现在只定义了一个带有一个spice
参数的spicy
方法.之后模板引用了这个方法.第一个按钮传递了一个常量chili
,另一个按钮则传递了一个模型属性cunstomSpice
(绑定在input标签上).
##scope继承示例
index.html
<div class="spicy">
<div ng-controller="MainController">
<p>Good {{timeOfDay}}, {{name}}!</p>
<div ng-controller="ChildController">
<p>Good {{timeOfDay}}, {{name}}!</p>
<div ng-controller="GrandChildController">
<p>Good {{timeOfDay}}, {{name}}!</p>
</div>
</div>
</div>
</div>
app.css
div.spicy div {
padding: 10px;
border: solid 2px blue;
}
app.js
var myApp = angular.module('scopeInheritance', []);
myApp.controller('MainController', ['$scope', function($scope) {
$scope.timeOfDay = 'morning';
$scope.name = 'Nikki';
}]);
myApp.controller('ChildController', ['$scope', function($scope) {
$scope.name = 'Mattie';
}]);
myApp.controller('GrandChildController', ['$scope', function($scope) {
$scope.timeOfDay = 'evening';
$scope.name = 'Gingerbread Baby';
}]);
注意三个ng-controller
指令是如何在模板中嵌套的.这将导致在我们的视图中创建四个作用域(scope):
- 根作用域
MainController
的作用域,包含了timeOfDay
和name
属性ChildController
作用域,继承了timeOfDay
属性但是覆盖了之前的name
属性GrandChildController
作用域,定义在MainController
中的timeOfDay
属性和定义在ChildController
中的name
属性
继承也同样适用于控制器方法.在之前的例子中所有的属性都可以被返回字符串的方法替代.
#测试控制器
有很多种途径可以去测试控制器,接下来展示的是最好的规范之一,涉及到注入$rootScope
和$controller
控制器定义
var myApp = angular.module('myApp',[]);
myApp.controller('MyController', function($scope) {
$scope.spices = [{"name":"pasilla", "spiciness":"mild"},
{"name":"jalapeno", "spiciness":"hot hot hot!"},
{"name":"habanero", "spiciness":"LAVA HOT!!"}];
$scope.spice = "habanero";
});
控制器测试
describe('myController function', function() {
describe('myController', function() {
var $scope;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('MyController', {$scope: $scope});
}));
it('should create "spices" model with 3 spices', function() {
expect($scope.spices.length).toBe(3);
});
it('should set the default value of spice', function() {
expect($scope.spice).toBe('habanero');
});
});
});
如果你需要测试一个嵌套的控制器,你就必须在测试中创建一个相同的被继承的作用域.
describe('state', function() {
var mainScope, childScope, grandChildScope;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
mainScope = $rootScope.$new();
$controller('MainController', {$scope: mainScope});
childScope = mainScope.$new();
$controller('ChildController', {$scope: childScope});
grandChildScope = childScope.$new();
$controller('GrandChildController', {$scope: grandChildScope});
}));
it('should have over and selected', function() {
expect(mainScope.timeOfDay).toBe('morning');
expect(mainScope.name).toBe('Nikki');
expect(childScope.timeOfDay).toBe('morning');
expect(childScope.name).toBe('Mattie');
expect(grandChildScope.timeOfDay).toBe('evening');
expect(grandChildScope.name).toBe('Gingerbread Baby');
});
});