AngularJS模块化和依赖注入
AngularJS带有内置的依赖项注入机制。我们可以将应用程序划分为AngularJS可以相互注入的多种不同类型的组件。模块化应用程序可以更轻松地重用,配置和测试应用程序中的组件。
AngularJS包含以下核心类型的对象和组件:
- Value
- Factory
- Service
- Provider
- Constant
可以使用AngularJS依赖项注入机制将这些核心类型相互注入。在本文的其余部分中,我将解释如何定义这些组件并将它们相互注入。
Value
AngularJS中的值是一个简单的对象。它可以是数字,字符串或者JavaScript对象。值通常用作注入工厂,服务或者控制器的配置。
值必须属于AngularJS模块。以下是向AngularJS模块添加值的三个示例:
var myModule = angular.module("myModule", []); myModule.value("numberValue", 999); myModule.value("stringValue", "abc"); myModule.value("objectValue", { val1 : 123, val2 : "abc"} );
这些值是使用模块上的" value()"函数定义的。第一个参数是值的名称,第二个参数是值本身。工厂,服务和控制器现在可以通过名称来引用这些值。
注入值
只需通过添加与该名称同名的参数(将值定义时传递给value()函数的第一个参数)添加到AngularJS控制器函数中即可。这是一个例子:
var myModule = angular.module("myModule", []); myModule.value("numberValue", 999); myModule.controller("MyController", function($scope, numberValue) { console.log(numberValue); });
请注意,控制器功能的第二个参数与该值的名称相同。
Factory
Factory是一个创建值的函数。当服务,控制器等需要从工厂注入的值时,工厂将按需创建值。一旦创建,该值可用于需要注入的所有服务,控制器等。因此,工厂与值的不同之处在于它可以使用工厂函数来创建它返回的对象。我们还可以将值注入工厂以供创建对象时使用。我们不能通过值来做到这一点。
这是一个在模块上定义工厂的示例,以及一个将工厂创建的值注入的控制器:
var myModule = angular.module("myModule", []); myModule.factory("myFactory", function() { return "a value"; }); myModule.controller("MyController", function($scope, myFactory) { console.log(myFactory); });
如我们所见,它与定义和注入值对象非常相似。请记住,注入的不是工厂功能,而是工厂功能产生的值。
将值注入工厂Factory
我们可以将值注入工厂。就像将值注入控制器一样。这是一个例子:
var myModule = angular.module("myModule", []); myModule.value("numberValue", 999); myModule.factory("myFactory", function(numberValue) { return "a value: " + numberValue; });
在此示例中,注入值用于创建由工厂功能创建的对象。
服务
AngularJS中的服务是一个单例JavaScript对象,其中包含一组函数。这些功能包含服务执行其工作所需的任何逻辑。
AngularJS服务是使用模块上的service()
函数创建的。这是一个例子:
function MyService() { this.doIt = function() { console.log("done"); } } var myModule = angular.module("myModule", []); myModule.service("myService", MyService);
如我们所见,服务的定义与工厂和值有所不同。首先,服务被定义为一个单独的命名函数。那是因为AngularJS中的服务是使用new
关键字创建的。因此,AngularJS将在内部执行此操作:
var theService = new MyService();
除了将服务定义为内部具有函数的函数外,还可以将它们添加到AngularJS并与AngularJS一起使用,就像使用值或者函数一样。我们将服务注入到控制器中,如下所示:
function MyService() { this.doIt = function() { console.log("done"); } } var myModule = angular.module("myModule", []); myModule.service("myService", MyService); myModule.controller("MyController", function($scope, myService) { myService.doIt(); });
将值注入服务
我们可以将值注入到服务中,就像可以将值注入到控制器中,或者将服务注入到控制器等中一样。这是一个示例:
var myModule = angular.module("myModule", []); myModule.value ("myValue" , "12345"); function MyService(myValue) { this.doIt = function() { console.log("done: " + myValue; } } myModule.service("myService", MyService);
注意," MyService"函数的参数的命名方式与在模块上注册的值相同。因此,该值将在创建时注入到服务中。
提供者
AngularJS中的提供者是我们可以创建的最灵活的工厂形式。我们可以像使用服务或者工厂一样向模块注册提供程序,只是使用provider()
函数。这是一个AngularJS提供程序示例:
var myModule = angular.module("myModule", []); myModule.provider("mySecondService", function() { var provider = {}; provider.$get = function() { var service = {}; service.doService = function() { console.log("mySecondService: Service Done!"); } return service; } return provider; });
如我们所见,provider()函数有两个参数。第一个参数是提供程序创建的服务/对象的名称。在这种情况下,名称为" mySecondService"。第二个参数是创建提供程序的函数。注意:提供程序本身就是工厂,因此目前没有从提供程序创建任何实际的服务或者对象。仅定义创建提供程序的功能。
当我们查看创建提供程序的函数时,可以看到该提供程序是一个JavaScript对象。
JavaScript提供程序对象包含单个$ get()函数。这是提供程序的工厂功能。换句话说,$ get()函数创建提供者创建的任何内容(服务,值等)。在上面的示例中,提供程序创建了一个服务对象,其中包含一个称为doService()
的单一服务函数(标准JavaScript函数)。
为了将提供程序的产品注入到控制器中,请像指定服务一样指定对提供程序的依赖性。注入控制器的是提供者创建的产品,而不是提供者本身。这是一个AngularJS提供程序注入示例:
myModule.controller("MyController", function($scope, mySecondService) { $scope.whenButtonClicked = function() { mySecondService.doIt(); } });
如我们所见,提供程序的名称在控制器功能中用作参数。由提供者的$ get()函数创建的对象将被注入到该参数中。
配置提供者
在模块的配置阶段,可以通过调用提供程序的功能来进一步配置提供程序。这是一个例子:
var myModule = angular.module("myModule", []); myModule.provider("mySecondService", function() { var provider = {}; var config = { configParam : "default" }; provider.doConfig = function(configParam) { config.configParam = configParam; } provider.$get = function() { var service = {}; service.doService = function() { console.log("mySecondService: " + config.configParam); } return service; } return provider; }); myModule.config( function( mySecondServiceProvider ) { mySecondServiceProvider.doConfig("new config param"); }); myModule.controller("MyController", function($scope, mySecondService) { $scope.whenButtonClicked = function() { mySecondService.doIt(); } });
请注意,提供程序对象现在如何具有一个名为doConfig()
的添加功能。此功能可用于在提供程序上设置配置参数。
还要注意对" myModule.config()"函数的调用。 config函数将一个函数作为参数。此功能可以配置模块。传递给config()的函数采用一个名为mySecondServiceProvider的参数。该名称与提供程序在后缀" Provider"中注册的名称相同。后缀告诉AngularJS注入提供程序本身,而不是注入提供程序创建的对象。在传递给config()函数的函数内部,调用了mySecondServiceProvider.doConfig()函数,该函数在提供程序上设置config参数。
在示例的后面定义的控制器仅取决于提供者(而不是提供者本身)创建的对象。为此,可以使用名为" mySecondService"的参数,该参数是向服务提供者注册的名称。如我们所见,该服务是在$ scope.whenButtonClicked()函数内部使用的。
常量
在关于提供程序的上一节中,我们看到了如何通过module.config()
函数配置提供程序。不幸的是,我们不能将值注入到module.config()函数中。相反,我们可以注入常量。
AngularJS中的常量是使用module.constants()函数定义的。这是一个AngularJS常量示例:
myModule.constant("configValue", "constant config value");
现在可以像下面这样将这个常量注入到module.config()
函数中:
myservices.config( function( mySecondServiceProvider, configValue ) { mySecondServiceProvider.doConfig(configValue); });
如我们所见,参数configValue匹配常量的名称,该常量也是configValue。因此,常数值将被注入该参数。然后,将常量值作为参数传递给mySecondServiceProvider提供程序上的doConfig()函数。
模块之间的依赖关系
如我们所见,值,工厂和服务已添加到AngularJS模块。一个模块可以使用另一模块的值,工厂和服务。为此,模块需要声明对模块的依赖关系,该依赖关系包含要使用的值,工厂和服务。这是一个例子:
var myUtilModule = angular.module("myUtilModule", []); myUtilModule.value ("myValue" , "12345"); var myOtherModule = angular.module("myOtherModule", ['myUtilModule']); myOtherModule.controller("MyController", function($scope, myValue) { });
注意第二个模块(myOtherModule)如何在传递给angular.module()函数的第二个参数(数组内部)中列出第一个模块的名称(myUtilModule)。这告诉AngularJS,在myUtilModule内部定义的所有值,工厂和服务也应该在myOtherModule模块内部可用。换句话说," myOtherModule"依赖于" myUtilModule"。
其次,请注意MyController控制器函数现在如何声明一个名为myValue的参数。该值将从" myUtilModule"模块上注册的值中提供。
AngularJS中的缩小安全依赖注入
当我们缩小JavaScript时,JavaScript缩小器会将本地变量和参数的名称替换为较短的名称。但是,AngularJS使用控制器函数,工厂,服务和提供程序的参数名称来决定向其工厂函数中注入什么。如果名称更改,AngularJS将无法注入正确的对象。
为了使AngularJS代码缩小安全,我们需要提供要作为字符串注入的对象名称。将这些字符串与需要注入值的函数一起包装在数组中。这是一个AngularJS缩小安全依赖项注入示例:
var myapp = angular.module("myapp", ['myservices']); myapp.controller("AController", ['$scope', function(p1) { p1.myvar = "the value"; }]);
这个例子将$ scope
对象注入到控制器函数的p1
参数中。
注意控制器功能是如何注册的。与其直接将控制器函数传递给angular.controller函数,不如传递一个数组。该数组包含要注入控制器功能的值的名称,以及控制器功能本身。控制器功能始终是此数组中的最后一个值。如果需要注入多个值,则值名称会在数组的开头列出,并按顺序将它们注入到函数中。这是一个缩小安全的多值示例:
var myapp = angular.module("myapp", ['myservices']); myapp.controller("AController", ['$scope', '$http', function(p1, p2) { p1.myvar = "the value"; p2.get("/myservice.json"); }]);
本示例将$ scope对象插入到p1参数中,并将$ http服务注入到控制器函数的p2参数中。
现在,控制器功能的参数名称不再重要。 AngularJS将使用数组开头的字符串来确定要向控制器函数中注入的内容。
可以将相同的机制用于工厂,服务和提供者以提供最小化的安全依赖项注入。这是一个缩小安全的工厂,服务和提供者的示例:
var myutil = angular.module("myutil", []); myutil.value("safeValue", "a safe value"); myutil.factory("safeFactory", ['safeValue', function(p1) { return { value : p1 }; }]); function MySafeService(p1){ this.doIt = function() { return "MySafeService.doIt() called: " + p1.value; } } myutil.service("safeService", ['safeFactory', MySafeService]); myutil.provider("safeService2", function() { var provider = {}; provider.$get = ['safeService', function(p1) { var service = {}; service.doService = function() { console.log("safeService from provider: " + p1.doIt()); } return service; }]; return provider; }); myapp.controller("AController", ['$scope', 'safeService2', function(p1, p2) { p1.myvar = "the value"; p2.doService(); }]);
请特别注意提供者的声明。注意,如何在提供者工厂函数中未指定依赖项,而是在提供者工厂函数内部返回的提供者的$ get()函数中进行指定。实际上,使用具有依赖项和函数实现名称的数组,而不是仅使用$ get()函数。除此之外,指定提供程序的依赖性与提供程序的工厂,服务和控制器功能相同。