Angular框架提供了强大的依赖注入机制,这一切都是有注入器(injector)完成. 注入器会自动实例化服务组件和符合Angular API规则的特殊对象,例如控制器,指令,过滤器动画等。
那注入器怎么知道如何去创建这些特殊的对象呢? Angular提供了5种方式让注入器创建对象,其中最基础的方式就是提供者(provider), 其余四种方式(Value, Factory, Service, Constant)都是基于Provider的语法糖。一个模块(module)就是一种配置注入器实例工厂的方式,我们也称为提供者(provider)。当Angular应用从一个模块开始启动时,Angular会为该模块创建注入器,注册在该模块以及所依赖的模块中定义的提供者,注入器会用提供者来创建app的对象。
让我们通过如下几个例子来进一步了解上述五种方式的用法:
例一: Value
var myApp = angular.module('myApp', []); myApp.value('clientId', 'a12345654321x');
myApp.controller('DemoController', ['clientId', function DemoController(clientId) { this.clientId = clientId; }]);
<html ng-app="myApp">
<body ng-controller="DemoController as demo">
Client ID: {{demo.clientId}}
</body>
</html>
在myApp模块中通过Value的方式定义了clientId值, 在后面的代码中就可以通过注入的方式在控制器中注入clientId的值。
例二:Factory
Value的方式有其局限性,只适合定义简单类型, 工厂方式则有更多的灵活性:
- 可以注入使用其他服务
- 服务初始化
- 惰性初始化
工厂方法返回service实例,注意Angular的服务是单例的。 让我们看如下代码:
myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) { var encrypt = function(data1, data2) { // NSA-proof encryption algorithm: return (data1 + ':' + data2).toUpperCase(); }; var secret = window.localStorage.getItem('myApp.secret'); var apiToken = encrypt(clientId, secret); return apiToken; }]);
我们通过工厂方法返回了一个加密过的token, 在第二个参数中我们注入了例一中的clientId.
例三:Service
在面向对象过程中我们通常使用自定义类型。该例中我们定义了UnicornLauncher构造方法,并通过service方法实例化unicornLauncher:
function UnicornLauncher(apiToken) { this.launchedCount = 0; this.launch = function() { // Make a request to the remote API and include the apiToken ... this.launchedCount++; } } myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);
例四: 提供者
提供者(Provider)为构造Angular应用的组件对象提供了最强大的支持。从语法层面讲Provider是$get方法的具体实现,是一个工厂方法类似例二的Factory. 对于构造service组件,使用上例中的Service方法就可以了,如果我们想给组件加上更多的启动配置项,增加组件的重用性,则考虑使用provider. 基于上面的例子我们给UnicornLauncher添加了“useTinfoilShielding”配置项:
myApp.provider('unicornLauncher', function UnicornLauncherProvider() { var useTinfoilShielding = false; this.useTinfoilShielding = function(value) { useTinfoilShielding = !!value; }; this.$get = ["apiToken", function unicornLauncherFactory(apiToken) { // let's assume that the UnicornLauncher constructor was also changed to // accept and use the useTinfoilShielding argument return new UnicornLauncher(apiToken, useTinfoilShielding); }]; }); myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) { unicornLauncherProvider.useTinfoilShielding(true); }]);
通过provider api,定义了UnicornLauncherProvider,其中$get方法中定义了实例化UnicornLauncher的工厂方法,实例化方法参数中包含了useTinfoilShielding参数,该值由myApp.config配置。即Angular通过UnicornLauncherProvider实例化UnicornLauncher service. 注意在config方法中通常只能注入Provider,在应用启动时,Angular首先会配置并实例化所有的Provider实例,然后根据Provider来创建其他组件,在config阶段,service还未被实例化,所以不能在config方法中访问service组件。在config阶段结束后便进入run阶段,就不能再访问provider了,angular会根据provider的定义来初始化组件。
例五:常量(Constant)
上述例子中可见Angular应用启动分为两个阶段: config和run. 在config阶段services是不可用的,也不能访问通过Value初始的值类。 对于应用级别的常量,在config和run阶段皆可见,我们可以通过Constant来定义,参考如下示例代码:
myApp.constant('planetName', 'Greasy Giant'); myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) { unicornLauncherProvider.useTinfoilShielding(true); unicornLauncherProvider.stampText(planetName); }]); myApp.controller('DemoController', ["clientId", "planetName", function DemoController(clientId, planetName) { this.clientId = clientId; this.planetName = planetName; }]);
我们定义了planetName常量,在config和controller中都可以访问。
<html ng-app="myApp">
<body ng-controller="DemoController as demo">
Client ID: {{demo.clientId}}
<br>
Planet Name: {{demo.planetName}}
</body>
</html>
特殊组件
前文中我们提到过,除了service组件,我们可以实现一些特殊组件,例如控制器,指令,过滤器和动画。这些组件可作为Angular框架的插件,实现了Angular框架特定的接口。这些接口也是基于Factory的。让我来看下面的一些例子:
myApp.directive('myPlanet', ['planetName', function myPlanetDirectiveFactory(planetName) { // directive definition object return { restrict: 'E', scope: {}, link: function($scope, $element) { $element.text('Planet: ' + planetName); } } }]);
<html ng-app="myApp">
<body>
<my-planet></my-planet>
</body>
</html>
上面例子中,我们通过directive来注册了myPlanet组件,和Factory一样,定义了myPlanetDirectiveFactory工厂方法,返回一个指令对象。同样我们也可通过工厂方法自定义指令和动画。
相当于其他类型的组件,控制器没有使用工厂方法,而是构造器,因而控制器不是单例的。
myApp.controller('DemoController', ['clientId', function DemoController(clientId) { this.clientId = clientId; }]);
让我们总结一些上面例子中的要点:
- 注入器会通过提供者创建两种类型的组件: 服务组件(services)和特殊组件(controllers, directives, filters, animations).
- Angular框架提供了5类提供者来创建对象: Value, Factory, Provider, Constant 和 Service, 其中Provider是最基础也是最强大的,另外四种都是基于Provider的封装。
- Factory和Service是最常用的, 两者的区别是Service方法只返回某类型的对象,而Factory支持Javascript原始类型和函数。
- Provider的用法相对复杂,除非需要为可重用组件提供全局的配置项,我们一般考虑使用其他的方式来构建组件。
- 除了控制器使用构造器,其余特殊组件都使用工厂方式,控制器可以有多个实例,而其余特殊组件都是单例的。
具体总结可参考如下表格(摘自官方文档):
Features / Recipe type | Factory | Service | Value | Constant | Provider |
---|---|---|---|---|---|
can have dependencies | yes | yes | no | no | yes |
uses type friendly injection | no | yes | yes* | yes* | no |
object available in config phase | no | no | no | yes | yes** |
can create functions | yes | yes | yes | yes | yes |
can create primitives | yes | no | yes | yes | yes |