1. 控制器的作用
我们知道,在AngularJS中,实现数据绑定的核心是scope对象。那么控制器又有什么用呢?
简单地说,没有控制器/controller,我们没有地方定义业务模型。
回忆下ng-init指令。我们可以使用ng-init指令在scope对象上定义数据,比如:
- <div ng-init="sb={name:'somebody',gender:'male',age:28}">
但是,ng-init的值是一个AngularJS表达式,没有办法定义方法。
控制器让我们有机会在scope上定义我们的业务逻辑,具体说,可以使用控制器:
1) 对scope对象进行初始化
2) 向scope对象添加方法
控制器的一次性
控制器构造函数仅在AngularJS对HTML文档进行编译时被执行一次。从这个角度看, 就更容易理解为何将控制器称为对scope对象的增强:一旦控制器创建完毕,就意味着scope对 象上的业务模型构造完毕,此后就不再需要控制器了- scope对象接管了一切。
控制器对scope的影响
ng-controller指令总是创建一个新的scope对象:
在图中,我们看到:
- ng-app指令引发$rootScope对象的创建。开始时,它是一个空对象。
- body元素对应的scope对象还是$rootScope。ng-init指令将sb对象挂在了$rootScope上。
- div元素通过ng-controller指令创建了一个新的scope对象,这个对象的原型是$rootScope。
- 因为原型继承的关系,在do函数中对sb的引用指向$rootScope.sb。
初始化$scope对象
通常在应用启动时,需要初始化scope对象上的数据模型。我们之前曾使用ng-init指令进行初始化, 而使用控制器则是更为规范的做法。
右边的示例定义了一个ezController,利用这个控制器,我们对业务模型进行了初始化赋值:
请注意,控制器仅仅负责在编译时在scope对象上建立视图对象vm,视图对象和模板的绑定则是由 scope负责管理的。
实例:
javascript:
var ezControllerClass = function($scope){
//view model
$scope.vm = {
sb : {
name : "Jason Stantham",
gender : "male",
age : 48,
career : "actor",
photo : "hiphotos.baidu.com/baike/w%3D268/sign=a03742145bee3d6d22c680cd7b176d41/359b033b5bb5c9eae4c45250d739b6003af3b34a.jpg"
}
};
};
angular.module("ezstuff",[])
.controller("ezController",ezControllerClass);
HTML:
<html ng-app="ezstuff">
<head>
<script src="angular.min.js"></script>
</head>
<body>
<div ng-controller="ezController">
<div>name : {{vm.sb.name}}</div>
<div>gender : {{vm.sb.gender}}</div>
<div>age : {{vm.sb.age}}</div>
<div>career : {{vm.sb.career}}</div>
<div><img ng-src="{{vm.sb.photo}}"></div>
</div>
</body>
</html>
建议:别把任何代码都塞到控制器里!
控制器的设计出发点是封装单个视图的业务逻辑,因此,不要进行以下操作:
应当将DOM操作使用指令/directive进行封装。
应当使用过滤器/filter对输出显示进行转化。
对于需要复用的基础代码,应当使用服务/service进行封装
六. 使用服务封装可复用的代码
1. 创建服务组件
在AngularJS中创建一个服务组件很简单,只需要定义一个具有$get方法的构造函数, 然后使用模块的provider方法进行登记:
- //定义构造函数
- var myServiceProvider = function(){
- this.$get = function(){
- return ....
- };
- };
- //在模块中登记
- angular.module("myModule",[])
- .provider("myService",myServiceProvider);
右边的示例定义了一个支持四则运算的服务:ezCalculator。你可以点击按钮【3+4=?】 查看效果。
javascript:
function doCalc(){
var injector = angular.injector(["ezstuff"]),
mycalculator = injector.get("ezCalculator"),
ret = mycalculator.add(3,4);
document.querySelector("#result").textContent = ret;
}
angular.module("ezstuff",[])
.provider("ezCalculator",function(){
this.$get = function(){
return {
add : function(a,b){return a+b;},
subtract : function(a,b){return a-b;},
multiply : function(a,b){return a*b;},
divide: function(a,b){return a/b;}
}
};
})
2.可配置的服务
有时我们希望服务在不同的场景下可以有不同的行为,这意味着服务可以进行配置。
比如,我们希望小计算器可以根据不同的本地化区域,给计算结果追加货币符号前缀, 那么需要在这个服务创建之前,首先配置本地化区域的值,然后在具体的计算中, 根据这个值选择合适的货币符号。
AngularJS使用模块的config()方法对服务进行配置,需要将实例化的服务提供者 (而不是服务实例)注入到配置函数中:
- angular.module("myModule",[])
- .config(["myServiceProvider",function(myServiceProvider){
- //do some configuration.
- }]);
注意:服务提供者provider对象在注入器中的登记名称是:服务名+Provider。 例如: $http的服务提供者实例名称是"$httpProvider"。
右边的示例嵌入了可配置的ezCalculator服务,查看代码体会一下!
javascript实例:
function doCalc(){
var injector = angular.injector(["ezstuff"]),
mycalculator = injector.get("ezCalculator"),
ret = mycalculator.add(3,4);
document.querySelector("#result").textContent = ret;
}
angular.module("ezstuff",[])
.provider("ezCalculator",function(){
var currency = "$";
this.setLocal = function(l){
var repo = {
"CN":"¥",
"US":"$",
"JP":"¥",
"EN":"€"
};
if(repo[l]) currency = repo[l];
};
this.$get = function(){
return {
add : function(a,b){return currency + (a+b);},
subtract : function(a,b){return currency + (a-b);},
multiply : function(a,b){return currency + (a*b);},
divide: function(a,b){return currency + (a/b);}
}
};
})
.config(function(ezCalculatorProvider){
ezCalculatorProvider.setLocal("CN");
});
3. 可配置的服务
有时我们希望服务在不同的场景下可以有不同的行为,这意味着服务可以进行配置。
比如,我们希望小计算器可以根据不同的本地化区域,给计算结果追加货币符号前缀, 那么需要在这个服务创建之前,首先配置本地化区域的值,然后在具体的计算中, 根据这个值选择合适的货币符号。
AngularJS使用模块的config()方法对服务进行配置,需要将实例化的服务提供者 (而不是服务实例)注入到配置函数中:
- angular.module("myModule",[])
- .config(["myServiceProvider",function(myServiceProvider){
- //do some configuration.
- }]);
注意:服务提供者provider对象在注入器中的登记名称是:服务名+Provider。 例如: $http的服务提供者实例名称是"$httpProvider"。
右边的示例嵌入了可配置的ezCalculator服务,查看代码体会一下!
javascript 实例代码:
function doCalc(){
var injector = angular.injector(["ezstuff"]),
mycalculator = injector.get("ezCalculator"),
ret = mycalculator.add(3,4);
document.querySelector("#result").textContent = ret;
}
angular.module("ezstuff",[])
.provider("ezCalculator",function(){
var currency = "$";
this.setLocal = function(l){
var repo = {
"CN":"¥",
"US":"$",
"JP":"¥",
"EN":"€"
};
if(repo[l]) currency = repo[l];
};
this.$get = function(){
return {
add : function(a,b){return currency + (a+b);},
subtract : function(a,b){return currency + (a-b);},
multiply : function(a,b){return currency + (a*b);},
divide: function(a,b){return currency + (a/b);}
}
};
})
.config(function(ezCalculatorProvider){
ezCalculatorProvider.setLocal("US");
});
4. 服务定义语法糖
使用模块的provider方法定义服务组件,在有些场景下显得有些笨重。AngularJS友好 地提供了一些简化的定义方法,这些方法通常只是对provider方法的封装, 分别适用于不同的应用场景:
使用一个对象工厂函数定义服务,调用该工厂函数将返回服务实例。
使用一个类构造函数定义服务,通过new操作符将创建服务实例。
使用一个值定义服务,这个值就是服务实例。
使用一个常量定义服务,这个常量就是服务实例。
4.1 factory方法
factory方法要求提供一个对象工厂,调用该类工厂将返回服务实例。
- var myServiceFactory = function(){
- return ...
- };
- angular.module("myModule",[])
- .factory("myService",myServiceFactory);
INSIDE:AngularJS会将factory方法封装为provider,上面的示例 等同于:
- var myServiceFactory = function(){
- return ...
- };
- angular.module("myModule",[])
- .provider("myService",function(){
- this.$get = myServiceFactory;
- });
右边预置了使用factory方法改写的ezCalculator示例,感受下和provider方法的区别!
javascript 部分代码:
angular.module("ezstuff",[])
.factory("ezCalculator",function(){
return {
add : function(a,b){return a+b;},
subtract : function(a,b){return a-b;},
multiply : function(a,b){return a*b;},
divide: function(a,b){return a/b;}
}
})
4.2 service方法
service方法要求提供一个构造函数,AngularJS使用这个构造函数创建服务实例:
- var myServiceClass = function(){
- this.method1 = function(){...}
- };
- angular.module("myModule",[])
- .service("myService",myServiceClass);
INSIDE:AngularJS会将service方法封装为provider,上面的示例 等同于:
- var myServiceClass = function(){
- //class definition.
- };
- angular.module("myModule",[])
- .provider("myService",function(){
- this.$get = function(){
- return new myServiceClass();
- };
- });
右边预置了使用service方法改写的ezCalculator示例,感受下和factory方法的区别!
Javascrpt 部分代码实例:
var ezCalculatorClass = function(){
this.add = function(a,b){return a+b;};
this.subtract = function(a,b){return a-b;};
this.multiply = function(a,b){return a*b;};
this.divide = function(a,b){return a/b;};
};
angular.module("ezstuff",[])
.service("ezCalculator",ezCalculatorClass);
4.3 value方法
有时我们需要在不同的组件之间共享一个变量,可以将这种情况视为一种服务: provider返回的总是变量的值。
value方法提供了对这种情况的简化封装:
- angular.module("myModule",[])
- .value("myValueService","cx129800123");
INSIDE:AngularJS会将value方法封装为provider,上面的示例 等同于:
- angular.module("myModule",[])
- .provider("myService",function(){
- this.$get = function(){
- return "cx129800123";
- };
- });
javascript 部分实例代码:
var showUserName = function(){
var injector = angular.injector(["ezstuff"]),
username = injector.get("ezUserName");
document.querySelector("#result").textContent = username;
};
angular.module("ezstuff",[])
.value("ezUserName","whoami");
4.4 value方法
有时我们需要在不同的组件之间共享一个常量,可以将这种情况视为一种服务: provider返回的总是常量的值。
constant方法提供了对这种情况的简化封装:
- angular.module("myModule",[])
- .constant("myConstantService","Great Wall");
和value方法不同,AngularJS并没有将constant方法封装成一个provider,而仅仅 是在内部登记这个值。这使得常量在AngularJS的启动配置阶段就可以使用(创建任何 服务之前):你可以将常量注入到模块的config()方法中。
在右边的示例中,你可以试着将constant()改成value(),看看还能正常运行吗?
实例代码如下:
function doCalc(){
var injector = angular.injector(["ezstuff"]),
mycalculator = injector.get("ezCalculator"),
ret = mycalculator.add(3,4);
document.querySelector("#result").textContent = ret;
}
angular.module("ezstuff",[])
.constant("ezCurrency","CN")
.provider("ezCalculator",function(){
var currency = "$";
this.setLocal = function(l){
var repo = {
"CN":"¥",
"US":"$",
"JP":"¥",
"EN":"€"
};
if(repo[l]) currency = repo[l];
};
this.$get = function(){
return {
add : function(a,b){return currency + (a+b);},
subtract : function(a,b){return currency + (a-b);},
multiply : function(a,b){return currency + (a*b);},
divide: function(a,b){return currency + (a/b);}
}
};
})
.config(function(ezCurrency,ezCalculatorProvider){
ezCalculatorProvider.setLocal(ezCurrency);
});