Angular-依赖注入


依赖注入(DI)是软件设计模式,用来处理组件怎样掌控它们的依赖关系。 
Angular注入子系统负责创建组件,解决它们的依赖关系,并把它们提供给其它需要的组件。

1)、使用依赖注入

依赖注入贯穿于Angular,可以在定义组件或给模提供   run  和 config   代码块时候使用它。
  • 比如 services, directives, filters, and animations组件是通过可注入的Factory方法或构造函数定义。这些组件可以依赖注入一些 "service" 和"value" 组件。

  • Controllers通过构造函数定义, 可以依赖注 "service" 和"value"组件,但它们也就可以作为特殊依赖关系提供方。参考 下面的一系列此类特殊依赖关系的 Controllers 。

  • run 方法接受一个可以依赖注入 "service", "value" 和"constant" 组件的函数。注意你不能在 run 块中注入 "providers" 。

  • config 方法接受一个可以依赖注入 "provider" 和"constant" 组件的函数。注意你不能在 configuration中注入"service" 或 "value" 组件。

参考Modules 获取详细的关于 run 和config 代码块的信息。

1.1)、Factory 方法

        跟定义 directive, service, 或 filter的方式一样定义factory 函数。factory 方法也带module注册。申明factories推荐的方式是:

angular.module('myModule', [])
.factory('serviceId', ['depService', function(depService) {
  // ...
}])
.directive('directiveName', ['depService', function(depService) {
  // ...
}])
.filter('filterName', ['depService', function(depService) {
  // ...
}]);

1.2)、Module 方法

可以为module指定函数在configuration时或run time时通过调用 config 和run 方法运行。这些函数可以被带依赖注入就像上面的 factory 函数。

angular.module('myModule', [])
.config(['depProvider', function(depProvider) {
  // ...
}])
.run(['depService', function(depService) {
  // ...
}]);

1.3)、Controllers

Controllers 是"classes" 或"constructor functions" 负责提供应用行为,可以支持在template中申明标记。申明 Controllers推荐的方式是使用数组表示:

someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
  ...
  $scope.aMethod = function() {
    ...
  }
  ...
}]);

不像services,在应用中可以有同类型controller的很多对象。

还有,额外的依赖对 Controllers也可用:

    • $scope: Controllers跟DOM的元素有关,因而可以访问 scope。 其他组件 (例如services) 只能访问 $rootScope service。
    • resolves: 若controller初始化为route的一部分,那任何被解析为route的一部分的值能注入到controller。

这里 $inject 数组中的值的顺序必须匹配 MyController 参数的顺序。

就像数组注解,你需要注意保持 $inject 跟函数申明的参数同步。

2)、依赖注解

Angular 通过注入器调用某些函数 (例如service factories 和 controllers)。你需要注解这些functions来让注入器知道该个妞function注入什么services。有三种方法来使用service名字信息来注解你的代码:

    • 使用内联数组注解(优选)
    • 使用 $inject 属性注解
    • 从函数参数名隐形注解(有注意事项)

2.1)、内联数组注解

这个是优选方法来注解应用组件。这是文档中例子写法。

例如:

someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
  // ...
}]);

这里传递一个数组,它的元素有字符串列表组成 (依赖关系名字)跟在function 后面。

当使用这种类型注解,注意保持注解数组与函数声明的参数同步。 

2.2)、$inject 属性注解

为了允许minifiers重命名function parameters并仍可以注入正确的services, function需要跟 $inject property一同注解。 $inject property释一个要注入的 service 名字数组。

var MyController = function($scope, greeter) {
  // ...
}
MyController.$inject = ['$scope', 'greeter'];
someModule.controller('MyController', MyController);

这种情况下, $inject 数组中值得顺序必须匹配 MyController.中参数的顺序。

就像数组注解,你需要保持 $inject 与函数声明的参数同步。 

2.3)、隐式注解

注意:  若打算   minify  代码,service 名字会被重命名并打算你的app。

最简单的掌控依赖关系的方法是设想函数参数即为所依赖的名字。

someModule.controller('MyController',function($scope, greeter){// ...});

给定一个函数,注入器可以通过测验函数的申明和检查参数名字来推断要注入的services名字。在上述例子,$scope 和 greeter 是两个需要被注入到函数的services 。

这个方法的一个优点是这没有任何名字数组与函数参数保持同步。你也可以自由排序依赖关系。

然而这个方法不会与JavaScript minifiers/obfuscators一起工作,因为他们重命名参数的方式。 

 ng-annotate 工具让你在应用中使用隐形依赖注解并在minifying之前自动添加内联数组注解。若你决定利用这个方法,你可以使用 ng-strict-di

因为这些注意事项。建议避免使用这个注解风格。

3)、使用严格的依赖注入

你可以在与 ng-app 同样的element添加 ng-strict-di directive , 来打开严格的 DI 模式:

 
   
<!doctype html>
<html ng-app="myApp" ng-strict-di>
<body>
  I can add: {{ 1 + 2 }}.
  <script src="angular.js"></script>
</body>
</html>

严格模式跑出错误每当 service 尝试使用 隐形注解。

参考这个module,包含 使用隐形DI的 willBreak service:

 
   
angular.module('myApp', [])
.factory('willBreak', function($rootScope) {
  // $rootScope is implicitly injected
})
.run(['willBreak', function(willBreak) {
  // Angular will throw when this runs
}]);

当 willBreak service初始化,Angular 会因为严格模式抛出错误。当使用类似 ng-annotate 工具来确保所有应用组件带有注解。

若正在使用人工引导,你能够在选项配置参数中提供 strictDi: true 来使用隐形DI:

 
   
angular.bootstrap(document, ['myApp'], {
  strictDi: true
});


3)、为何依赖注入?

这段深入解释Angular的DI使用。关于怎么用请看上文。

为深入探讨 DI,参考维基百科Dependency Injection ,Martin Fowle写的 Inversion of Control ,或者在你的软件设计模式书中阅读 DI 。

只有三种方式一个 component (object or function) 能掌握它的依赖关系:

    1. component 能够创建依赖,通常使用 new 操作。
    2. component 能够查询依赖,通过引用一个全局变量。
    3. component 能够在它需要的地方有依赖传过来。

前两个方法创建或查找依赖不是最优选,因为它们在component中硬编码。为了改变依赖关系会带来困惑。若不是不可能, 要修改依赖关系。尤其测试中更有问题,在那它经常为了测试隔离需要提供模拟关系。

第三个方法最可行,因为它去掉了从component定位依赖关系的义务。依赖关系仅仅传递给component。

 
   
function SomeClass(greeter) {
  this.greeter = greeter;
}

SomeClass.prototype.doSomething = function(name) {
  this.greeter.greet(name);
}

上面例子 SomeClass 不要关心创建或定位 greeter 依赖,仅仅greeter初始化的时候传递。

这个示意图, 但它把掌控依赖的责任推给构造SomeClass.的代码。

为了管理dependency 创建的责任,每个 Angular 应用都有一个 injector。 injector 是一个 service locator 负责构造并查询依赖关系。

这个例子使用 injector service:

 
   
// Provide the wiring information in a module
var myModule = angular.module('myModule', []);

教 injector 怎么构建 greeter service。注意 greeter 依赖 $window service。 greeter service 是一个包含 greet method的对象。

 
   
myModule.factory('greeter', function($window) {
  return {
    greet: function(text) {
      $window.alert(text);
    }
  };
});

创建一个新的injector 能提供定义在 myModule module的components 并从injector申请 greeter service。(通常由 angular bootstrap自动完成)。

 
   
var injector = angular.injector(['ng', 'myModule']);
var greeter = injector.get('greeter');

要求依赖关系,解决了硬编码问题,但它也意味着 injector 需要始终传到应用。传递 injector 破坏了 Law of Demeter。为了补救,在HTML templates中使用declarative 符号,来担当越过injector创建的责任,例如此例:

 
   
<div ng-controller="MyController">
  <button ng-click="sayHello()">Hello</button>
</div>
function MyController($scope, greeter) {
  $scope.sayHello = function() {
    greeter.greet('Hello World');
  };
}

当Angular编译 HTML,它处理 ng-controller directive,反过来要求 injector创建  controller 和它的依赖关系的对象。

injector.instantiate(MyController);

所有都在幕后完成,注意拥有 ng-controller 要求njector初始化 class,它可以满足 MyController 所有的i依赖关系,而不需要controller熟悉injector。

最佳结果,应用代码code 仅仅申明它的依赖关系,而不用跟 injector交涉。这种设置不会破坏 Law of Demeter。

Note: Angular 使用  constructor injection.

猜你喜欢

转载自blog.csdn.net/ioniconline/article/details/50405731