AngularJs 的依赖注入
1-1 依赖注入介绍
依赖注入的介绍
“依赖”:当一个对象在创建的时候必须依赖另外一个对象。Exp. Var a = newA(); a依赖A。
“注入”:生命依赖关系之后,angular通过injector注入器所依赖的对象进行“注入”操作。
依赖注入的原理
每个Angular应用都存在一个injector注入器来处理依赖关系,注入器就是一个负责帮你查找和创建依赖的服务定位器。注入器对象具有get函数,可以获得任何被定义过的服务实例。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>依赖注入</title> <script src="components/angular/angular.min.js"></script> <style> .body{ font-size: 5em; } .show{ border: solid 1px black; } </style> </head> <body ng-app="a4_1"> <div ng-controller="c4_1"> <div class="{{cls}}">{{show}}</div> <button ng-click="onClick()">click here</button> </div> <script> var a4_1 = angular.module('a4_1',[]); a4_1.config(function($controllerProvider){ $controllerProvider.register('c4_1',['$scope',function($scope){ $scope.cls = ''; $scope.onClick = function(){ $scope.cls = 'show'; $scope.show = 'click here to show the information'; } }]); }); </script> </body> </html>
代码解析
创建模块之后我们并没有调用模块的控制器函数controller,而是调用config函数进行服务注册,这是因为在实际的代码执行过程,下面的代码执行后的功能是相同的。
a4_1.controller('c4_1',['$scope',function($scope){ //控制器代码 }]);
a4_1.config(function($controllerProvider){ $controllerProvider.register('c4_1',['$scope',function($scope){ //控制器代码 } }]); })
在angular中执行的本质是第二段代码,在Angular中可以通过模块中的config函数来声明需要注入的依赖对象,而声明的方式就是通过provider的服务。但在Angular内部controller控制器是通过controllerProvider服务创建的。因此用户在创建一个控制的时候是在config中调用controllerProvider服务的register方法,来完成一个控制器的创建,控制器创建之后,则再调用injector注入器完成各个依赖对象的注入。
1-2依赖注入的实例
代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="components/angular/angular.min.js"></script> <style> .show{ border: solid 1px black; } </style> </head> <body ng-app="a4_2"> <div ng-controller="c4_2"> <div> <div class="{{cls}}">{{text}}</div> <button ng-click="onClick(1)">morning</button> <button ng-click="onClick(2)">morning up</button> <button ng-click="onClick(3)">afternoon</button> <button ng-click="onClick(4)">evening</button> </div> </div> <script> var a4_2 = angular.module('a4_2',[]); a4_2.config(function($provide){ $provide.provider('show_1',function(){ this.$get = function(){ return { val : function(name){ return name; } } } }); }); a4_2.config(function($provide){ $provide.factory('show_2',function(){ return { val:function(name){ return name; } }; }); }); a4_2.config(function($provide){ $provide.value('show_3',function(name){ return name; }); }); a4_2.config(function($provide){ $provide.service('show_4',function(){ var self = {}; self.val = function(name){ return name; } return self; }) }); a4_2.controller('c4_2',function($scope,show_1,show_2,show_3,show_4){ $scope.cls = ''; $scope.onClick = function(t){ $scope.cls = 'show'; switch (t) { case 1: $scope.text = show_1.val('good morning'); break; case 2: $scope.text = show_2.val('good morning up'); break; case 3: $scope.text = show_3('good afternoon'); break; case 4: $scope.text = show_4.val('good evening'); break; default: break; } } }); </script> </body> </html>
知识点
在定义模版中的控制器层代码时,将这些定义好的变量全部作为依赖注入变量来使用。当页面在解析这段代码时,angular将启动$provide,并返回多个与服务一一对应的实例,通过这些实例来分别处理这些注入变量对应的函数功能,但对于开发者来说,只要注入变量就可以。
1-3 依赖注入标记
推断式注入
介绍
在没有明确声明下面,Angular会认定参数名称就是依赖注入的函数名,并在内部调用函数对象的toString()方法,获取对应的参数列表。
代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>推断注入</title> <script src="components/angular/angular.min.js"></script> </head> <body ng-app="a4_3"> <div ng-controller="c4_3"> <input id="btnAlert" type="button" value="alert the infor" ng-click="onClick('alert sucecss')"> </div> <script> var a4_3 = angular.module('a4_3',[]) .factory('$show',function($window){ return{ show : function(text){ $window.alert(text); } } }) .controller('c4_3',function($scope,$show){ $scope.onClick = function(msg){ $show.show(msg); } }) </script> </body> </html>
解释
在编写应用控制器代码时,由于在注入服务过程,没有使用[]或进行标记式的注入生命,因此注入器inject根据参数的名称来推断服务和控制器的关系。
angular自动通过annotate函数自动提取实例化参数时传递来的列表,并最终通过注入器将这些列表注入控制器中。需要说明的是:这种注入方式不需要关注注入时参数的先后顺序,angular会根据依赖的程度自动处理,由于angular需要根据参数列表分析注入服务,因此,这种注入的方式不能处理压缩或者混淆后的代码,只能处理源码。
标记式注入
代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>推断注入</title> <script src="components/angular/angular.min.js"></script> </head> <body ng-app="a4_3"> <div ng-controller="c4_3"> <div>{{text}}</div> <input id="btnAlert" type="button" value="alert the infor" ng-click="onShow('alert sucecss')"> <input id="btnAlert" type="button" value="write the infor" ng-click="onWrite('write sucecss')"> </div> <script> var c4_3 =function($scope,$show,$write){ $scope.onShow = function(msg){ $show.show(msg); } $scope.onWrite = function(msg){ $scope.text = $write.write(msg); } } c4_3.$inject = ['$scope','$show','$write']; var a4_3 = angular.module('a4_3', []) .factory('$show', function ($window) { return { show: function (text) { $window.alert(text); } } }) .factory('$write', function () { return { write: function (text) { return text; } } }) .controller('c4_3', c4_3) </script> </body> </html>
解释
控制函数通过调用$inject属性,向函数中注入了3个服务,注入服务名和顺序必须与函数在构造时的参数名和顺序完全一致,否则,将出现错误异常。
行内式注入
介绍
行内注入指的是在构建一个Angular对象时,允许开发人员将一个字符串数组作为对象的参数,而不仅仅是一个函数,在这个数组中,除最后一个必须是函数体外,其余代表的都是注入对象的服务,而他们的名称和顺序与最后一个函数的参数是一一对应的。
代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>行内注入</title> <script src="components/angular/angular.min.js"></script> </head> <body ng-app="a4_3"> <div ng-controller="c4_3"> <input id="btnAlert" type="button" value="alert the infor" ng-click="onClick('alert sucecss')"> </div> <script> var a4_3 = angular.module('a4_3',[]) .factory('$show',function($window){ return{ show : function(text){ $window.alert(text); } } }) .controller('c4_3',['$scope','$show',function($scope,$show){ $scope.onClick = function(msg){ $show.show(msg+'1'); } }]) </script> </body> </html>
解释
在注入的函数体中声明与服务名一一对应的参数,以用于函数体内部的调用,由于这种方式仍然是分析并处理注入字符数组中的内容,因此,即使是压缩或混淆后的代码,这种方式可以使用。
$injector常用API
has和get方法
injector.has(name)
injector 为获取的$injector对象,name为需要查找的服务名称。执行上述代码,返回一个布尔值,true为找到名称对应的服务,false表示没有找到。
Injector.get(name)
injector 为获取的$injector对象,name为需要查找的服务名称。执行上述代码后,将直接返回一个服务实例。
示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="components/angular/angular.min.js"></script> </head> </head> <body ng-app="a4_6"> <div ng-controller="c4_6"> </div> <script> var a4_6 = angular.module('a4_6',[]) .factory('$custom',function(){ return { print:function(msg){ console.log(msg); } } }); var injector = angular.injector(['a4_6','ng']); var has = injector.has('$custom'); console.log(has); if(has){ console.log('has方法实现'); var custom = injector.get('$custom'); custom.print('get方法实现'); } a4_6.controller('c4_6',['$scope','$custom',function($scope,$custom){ $custom.print('controller调用'); }]); </script> </body> </html>
解析:
var injector = angular.injector([‘a4_6’,’ng’]); 这句代码是为了注册服务。
invoke方法
介绍
invoke 是一个比较强大的API,他最为常用的场景就是执行一个自定义函数。除此之外,在执行函数的时候,还能传递变量给函数自身,调用格式如下:
injector.invoke(fn,[self],[locals])
Injector 是获取的$injector对象,参数fn为需要执行的函数名称,可选参数self 是一个对象,表示用于函数的this变量,可选项参数locals也是一个对象,他也能为函数中的变量名的传递提供方法支持。
代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="components/angular/angular.min.js"></script> </head> </head> <body ng-app="a4_7"> <div ng-controller="c4_7"> </div> <script> var a4_7 = angular.module('a4_7',[]) .factory('$custom',function(){ return { print:function(msg){ console.log(msg); } } }); var injector = angular.injector(['a4_7','ng']); var has = injector.has('$custom'); console.log(has); //非显示指定注入项 var invokTest = function($custom){ $custom.print('invok success'); }; if(has){ console.log('has方法实现'); // injector.invoke(invokTest); //invoke 显示指定注入项,AngularJS采用依赖项数数组方法解决代码压缩混淆注入的问题 injector.invoke(['$custom',function($custom){ this.text = 'invok success1'; $custom.print(this.text); }],self); } a4_7.controller('c4_7',['$scope','$custom',function($scope,$custom){ $custom.print('controller调用'); }]); </script> </body> </html>
解析
使用注入器的invoke()方法,可以直接调用一个用户自定义的函数体,并通过函数参数注入进来的对象.
angular.injector(['ng']) .invoke(function($http){ // do sth. with $http });
也可以使用注入器的get()方法,获得指定名称的服务实例:
var my$http = angular.injector(['ng']).get('$http'); 2 // do sth. with my$http
当采用invoke方式调用组件服务时,AngularJS通过检查函数的参数名确定需要注入什么对象
angular.injector(['ng']) .invoke(function($http){ // do sth. with $http });
AngularJS执行invoke时,检查函数的参数表,发现并注入$http对象。
这样有一个问题,就是当我们对JavaScript代码进行压缩处理时,$http可能会被变更成其他名称,这将导致注入失败。
显示指定注入项
AngularJS采用依赖项数数组方法解决代码压缩混淆注入的问题:
angular.injector(['ng']) .invoke(["$http","$compile",function($http,$compile){ // do sth. with $http,$compile }]);
这是传入invoke的是一个数组,数组的最后一项是实际要执行的函数,数组的其他项 则指明需要向该函数注入的对象。invoke将按照数组中的顺序,依次向函数注入依赖对象。也就是说上面代码中绿色部分,http,,http,compile是可以为任意名称的。