作者 CraftsCoder
冷月无声 - 博客频道 - CSDN.NET
http://blog.csdn.net/jaytalent/article/details/50986402
本文结合一些资料,谈谈AngularJS的依赖注入机制。主要参考资料有:
1. AngularJS官方文档:https://docs.angularjs.org/guide/di
2. Github文章:https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
3. 专著:Pro AngularJS
4. AngularJS 源代码
一、关于依赖注入
依赖注入式AngularJS的重要特性之一,有关概念和定义参考维基百科。依赖注入简化了Angular解析模块/组件之间依赖的过程。通常一个组件要获得它的依赖,有三种方式:
- 直接创建出依赖,如使用new操作符
- 能够查找到依赖,如引用全局变量
- 在需要的地方传入依赖
- var injector = angular.injector(['ng', 'myApp']);
- return {
- invoke: invoke,
- instantiate: instantiate,
- get: getService,
- annotate: createInjector.$$annotate,
- has: function(name) {
- return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
- }
- };
其中前三个方法很重要,例如可以通过get方法获得一个需要注入的组件:
- var comp = injector.get('component');
在Angular中,依赖注入可谓无孔不入。通常在两种场景(函数)下会使用到依赖注入:
- 工厂方法定义的组件(components):如directive,factory,filter,provider,controller等。这些工厂函数需要注册到某个模块上。controller比较特殊,它虽然也是一种组件,但是特别之处是它与某个DOM元素关联,因此可以注入$scope service,而其他组件只能注入$rootScope service。
- 模块提供的run/config方法。
- myApp.controller('smallCatCtrl', ['$scope', function($scope){
- $scope.sayCat = function(){
- alert('I Love Circle!');
- }
- }]);
- var MyController = function($scope, myService) {
- // ...
- }
- MyController.$inject = ['$scope', 'myService'];
- myApp.controller('MyController', MyController);
- myApp.controller('smallCatCtrl', function($scope){
- $scope.sayCat = function(){
- alert('I Love Circle!');
- }
- });
- var myFunction = function(hello) {
- hello('My Cat');
- };
- $injector.invoke(myFunction);
- injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
- function bootstrapApply(scope, element, compile, injector) {
- scope.$apply(function() {
- element.data('$injector', injector);
- compile(element)(scope);
- });
- }]
- );
- myApp.config(function($provide) {
- $provide.provider('sayHello', function() {
- this.$get = function() {
- return function(name) {
- alert("Hello, cat " + name);
- };
- };
- });
- });
- myApp.config(function($provide) {
- $provide.factory('sayCat', function() {
- return function(name) {
- alert("Hello, Cat" + name);
- };
- });
- });
- myMod.factory("sayHello", ...);
- myMod.service("sayHello", ...);
- myMod.value("Cat", ...);
下面说说config/run方法。Angular在加载模块时经过两个阶段:config和run。传入config函数的方法会在当前模块加载时执行;传入run函数的方法会在所有模块加载结束后执行。因此,在config阶段即可配置多个provider,但是在config阶段,只有provider可以注入,因此自定义的service无法注入到config中。这也好理解,因为config阶段是对service进行配置的而不是使用service本身。在前面的代码中,$provide service本身就是一个provider(对于Angular应用来讲相当于一个'元provider'),在模块加载时会调用$provide的provider方法定义一个新的provider。再看一个具体的例子:
- define(['angular'], function(angular){
- return angular.module('myCat', [])
- .provider('hello', function() {
- var firstName = '';
- this.makeName = function(first){
- firstName = first;
- }
- this.$get = function() {
- return function(name) {
- alert("Hello, " + firstName + ' ' + name);
- };
- };
- })
- .config(function(helloProvider){
- helloProvider.makeName('Circle');
- })
- .controller('catCtrl', [
- '$scope',
- '$q',
- 'hello',
- function($scope, $defer, hello){
- $scope.catName = "";
- $scope.sayHello = function(){
- hello('Jiang');//output 'Hello, Circle Jiang'
- };
- }]
- );
- });
在myCat模块中定义了一个名为hello的provider。该provider提供了一个makeName配置方法,用于设置firstName。$get方法返回一个函数用于打印一句话,这是该provider的核心功能(记住provider具体化后仍然是一个service,用于完成某种工作,只不过它是可配置的)。在config方法中,注入的函数参数对象名为系统生成的provider的名字helloProvider,自动添加了'provider'后缀。这里调用配置方法,这个配置会在未来使用该provider提供的功能时生效。最后,在controller定义中,注入了hello provider,并在sayHello方法中调用之,完成功能。这种模式应用十分广泛,一个例子是开源项目ui-router中的$state service以及对应的$stateProvider(provider的$get方法返回了一个$state对象,定义了go等方法和属性,使用过的开发者应该很熟悉了)。只有provider类型的service可以注入到其他组件中供其使用(如factory,constant等)。