断天涯大虾 发表于 2016-8-17 10:31:34

详细解读AngularJS Providers

本帖最后由 断天涯大虾 于 2016-9-26 10:59 编辑

http://images.cnitblog.com/blog/139239/201501/211111026888084.png
供应者(Providers)你创建的任何 Web 应用都是一些互相依赖的对象组合。这些对象需要被实例化并被绑定在一起工作。在 Angular 应用中,这些对象通过注入器服务自动完成实例化和绑定。
注入器创建了两种类型的对象,services(服务)和 specialized objects(特殊对象)。
服务等同于对象,它的 API 由编写服务的开发者定义。
特殊对象服从一套专门的 Angular 框架 API。这些对象是控制器、指令、过滤器或动画效果中的一个。
注入器需要知晓如何去创建这些对象。你通过注册一个“recipe(配方)” 来告诉注入器去创建你的对象。共有五种类型的配方。
最繁琐,也是功能最全面的是 Provider recipe。剩下的四种类型——Value,Factory,Service 和 Constant —— 仅仅是 Provider recipe 的语法糖。
接下来,我们看看如何在不同场景下通过不同的 recipe types 创建和使用 services 。我们将从最简单的例子开始,通过 Value recipe在代码**享一个字符串。 Note: A Word on Modules注意:模块概要
为了让注入器知晓如何创建和绑定所有的对象,它需要一个"recipes"的注册表。每个 recipe 都有唯一的对象标识符和以及何创建这个对象的描述。
每个 recipe 属于一个 Angular 模块。Angular 模块是一个保存了一个或多个 recipes 的袋子。手动跟踪模块依赖关系显然不是一个好方法,因此一个模块也可以包含依赖其它模块的相关信息。
一个 Angular 应用开始于一个给定的应用模块时,Angular 会创建一个新的注入器实例,进而按照所有核心"ng"模块、应用模块和在它的依赖中统一定义的 recipes 来创建一个 recipes 的注册表。然后,注入器通过查询 recipes 注册表来创建应用所需的对象。
变量方式(Value Recipe)假定我们想要获得一个非常简单的 service 叫做"clientId",它提供一个字符串用于表示某些远程 API 的认证 id。你可以这样去定义它:var myApp = angular.module('myApp', []);
myApp.value('clientId', 'a12345654321x');注意,我们创建一个名为 myApp 的 Angular 模块,然后指定了一个包含构建 clientId service 的配方,这只是一个字符串的简单例子。
以下是如何通过 Angular数据绑定来显示它:在这个例子中,我们使用了 Value recipe 去定义这个 value,提供给 DemoController 请求这个服务的 id "clientId"。
更复杂的例子!
工厂方式(Factory Recipe)这个 Value recipe 写起来非常简单,但缺乏创建 service时经常用到的一些重要特征。现在让我们看看 Value recipe 更强大的兄弟---Factory。Factory recipe 增加了以下能力:
[*]能够使用其它的 services(依赖)
[*]service 初始化
[*]延迟/懒惰初始化

Factory recipe通过一个包含有零个或多个参数(它所依赖的其它 services)的方法构造一个新的 service。这个方法的返回值是由 recipe 创建的这个服务的实例。
注意:Angular 中所有的服务都是单例模式。这意味着注入器创建这个对象时,仅使用一次recipe。然后注入器缓存所有将来需要的引用。
因为 Factory 是Value recipe 更强大的版本,你可以构造和它一样的服务。使用我们之前的 clientId Value recipe 的例子,可以采用 Factory recipe 这样重写:myApp.factory('clientId', function clientIdFactory() {
return 'a12345654321x';
});

但考虑到令牌仅仅是一个字符串常量,使用 Value recipe 更恰当,也更易于代码的阅读。
比如说,我们想创建一个用于计算远程 API 认证令牌的服务。这个令牌将被称做 apiToken,并计算基于 clientId 的值,然后加密存储于浏览器 Local Storage 中: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;
}]);
在上面的代码中,我们看到了如何通过工厂方法定义这个依赖于 clientId 服务的 apiToken 服务。这个工厂服务使用 NSA-proof 加密去产生一个认证令牌。
注意:工厂方法命名的最佳实践类似于<serviceId>Factory (e.g. apiTokenFactory)。虽然这种命名习惯不是必须的,但它有助于代码库导航或查看调试器的堆栈跟踪。
与 Value recipe 一样,Factory recipe 能够创建任何类型的服务,对象常量,方法,甚至一个自定义类型的实例。
服务方式(Service Recipe)JavaScript 开发者常常使用自定义的类型去编写面向对象的代码。让我们探究如何通过 unicornLauncher 服务发射一个 unicorn(独角兽)进入太空,这是一个自定义类型的实例:function UnicornLauncher(apiToken) {
this.launchedCount = 0;
this.launch = function() {
// make a request to the remote api and include the apiToken
...
this.launchedCount++;
}
}我们现在来准备发射 unicorn,但请注意 UnicornLauncher 依赖了我们的 apiToken。我们可以使用 Factory recipe 来满足这个 apiToken的依赖:myApp.factory('unicornLauncher', ["apiToken", function(apiToken) {
return new UnicornLauncher(apiToken);
}]);就是这样。不过,使用 Service recipe 才是最为恰当的例子。这个 Service recipe 产生一个类似于 Value 和 Factory recipes ,但它通过调用构造函数去执行 new 操作。这个构造函数可以接受零或多个参数,表示这个类型实例所需的依赖项。
注意:Service recipes 的设计模式被称之为构造函数注入。
因为我们已经有了一个 UnicornLauncher 类型的构造函数,就能够像这样去用 Service recipe 替换 Factory recipe:myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);多么简单!
注意:是的,我们有一个被称为“Service”的 service recipes。很遗憾我们将因为 mis-deed 而遭到报应。这就像是给我们某一个后代起名叫“小盆友”。这么搞会让老师们困惑。
供应者方式(Provider Recipe)还有两个 recipe 类型。它们都相当特殊,很少使用。如前述,Provider recipe 是核心 recipe 类型,所有其他的配方类型只是基于它的语法糖。它是最详细功能最多的 recipe,但对于大多数服务来说,它是多余的。
Provider recipe 是语法定义为一个自定义类型,实现 $get 的方法。这个方法是一个工厂方法,就像我们在 Factory recipe 中使用的一样。事实上,如果你定义一个 Factory recipe,钩子会自动创建一个包含空 Provider 类型 $get 方法的工厂方法。
只有当你希望一个应用程序配置的 API 必须在应用程序启动之前被创建,你才应该使用 Provider recipe 。通常只关注可重用服务的行为可能在应用程序间略有不同。
我们假设 unicornLauncher是个很不错的服务,许多应用程序都在使用它。默认情况下,launcher 发射 unicorns 进入太空时没有任何防护屏蔽。但某些行星的大气层实在是太厚了,以至于我们在星际旅行发射之前,必须用锡纸包住每一个 unicorns,否则它们将会在穿越大气层时烧毁。如果我们可以按照应用程序的需要去为 launcher 的每次发射配置使用锡箔屏蔽罩,那就再好不过了。我们可做如下配置: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);
}];
});为了在我们的应用程序中启用锡纸屏蔽罩,我们需要通过模块的 API 创建一个含有注入 UnicornLauncherProvider 的配置方法:myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) {
unicornLauncherProvider.useTinfoilShielding(true);
}]);
请注意 unicorn provider 被注入的配置方法,这种注入是通过 provider 的注入器完成的,不同于普通的实例注入器只是由 provider 实例进行实例化和绑定(注入)。
在应用程序启动期间,Angular 创建的所有服务前,配置和实例化所有的 providers。我们称之为应用程序生命周期中的配置阶段。在此阶段服务还不可用,因为它们还没有被创建。
配置阶段结束,providers 交互被禁止,开始创建 services。我们称这一阶段为应用程序生命周期的运行阶段。
常量方式(Constant Recipe)我们已经学会了如何区分应用程序生命周期中的配置阶段和运行阶段,如何通过配置方法向您的应用程序提供配置。由于配置方法运行在配置阶段,此时尚无服务可用,因此它不能访问任何对象,哪怕是通过 Value recipe 创建的 value 对象。
而简单的值,比如 url 的前缀,没有依赖或配置,需要在配置和运行阶段皆可使用。这就是 Constant recipe。
假设 unicornLauncher 服务可以标出 unicorn 发射自哪颗行星,而且这个行星的名字需要在配置阶段提供。同时,星球的名字会由应用程序指定,并且被多个控制器在运行阶段使用。
我们可以按照如下方式定义这个星球的名字为一个常量:myApp.constant('planetName', 'Greasy Giant');
我们这样可以配置 unicornLauncherProvider:myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) {
unicornLauncherProvider.useTinfoilShielding(true);
unicornLauncherProvider.stampText(planetName);
}]);
由于在运行阶段,Constant recipe 创建的值和 value recipe 一样,所以我们也可以在控制器和模板中使用它:myApp.controller('DemoController', ["clientId", "planetName", function DemoController(clientId,planetName) {
this.clientId = clientId;
this.planetName = planetName;
}]);

<html ng-app="myApp">
<body ng-controller="DemoController as demo">
Client ID: {{demo.clientId}}
<br>
Planet Name: {{demo.planetName}}
</body>
</html>
特殊目的对象(Special Purpose Objects)如上所述,我还有不同于 services,用于特殊目的对象。这些扩展作为框架的插件,因此必须实现 Angular 指定的接口。这些接口是:控制器、指令、过滤器和动画效果。
举例说明注入器创建特殊对象(控制器对象除外)使用 Factory recipe。
让我们看一下如何通过指令 api 创建一个非常简单的组件,取决于我们刚才 planetName 定义的常量和行星的名字,在我们的例子中:“行星名称:Greasy Giant”(油腻巨人)。
自从通过工厂方法注册指令之后,我们就可以使用与 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>
使用 Factory recipes,你还可以定义 Angular 的过滤器和动画效果,但是控制器有些许特殊。创建一个控制器作为自定义类型,声明包含作为其依赖项参数的构造函数。然后注册这个构造函数到一个模块。让我们看看 DemoController 先前的例子:myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
this.clientId = clientId;
}]);DemoController 是根据应用程序的需要,通过其构造函数实例化的(在我们的简单应用中只有一次)。与服务不同,控制器并不是单例的。构造函数被所有请求的服务调用,在我们的案例中是 clientId service。
结论(Conclusion)概括上述内容,让我来总结一下最重要的几点:
[*]注入器通过使用 recipes 来创建两种类型的对象:服务和特殊目的对象。
[*]一共有五种类型的 recipe 用于定义如何创建对象:变量、工厂、服务、供应者和常量。
[*] 工厂和服务是最常用的方式。两者仅有的不同是服务方式对于自定义类型对象效果更好,而工厂方式可以提供 JavaScript 基元和方法。
[*] 供应者方式是核心方式,所有其它方式都是它的语法糖。
[*]供应者是最复杂的方式类型。除非你正在构建一段需要全局配置的可重用代码,否则不要使用它。

所有特殊目的对象都通过工厂方式来定义,除了控制器。
Features / Recipe typeFactoryServiceValueConstantProvider
can have dependencies
支持依赖注入yesyesnonoyes
uses type friendly injection
使用友好的注入方式noyesyes*yes*no
object available in config phase
配置阶段可用nononoyesyes**
can create functions/primitives
可以创建方法/基元yesnoyesyesyes

以使用 new 操作符初始化为代价。
服务对象在配置阶段不可用,除了供应者实例(参见上面的 unicornLauncherProvider 示例)
支持JavaScript的开发工具目前已有部分产品支持AngularJS,Wijmo 就是其中之一。它是为企业应用程序开发而推出的一系列包含HTML5和JavaScript的开发控件集,无论应用程序是移动端、PC端、还是必须要支持IE6,Wijmo 均能满足需求。
开发工具推荐
[*]ComponentOne Studio Enterprise 是一款专注于企业应用的.NET全功能控件套包,支持WinForms、WPF、UWP、ASP.NET MVC等多个平台,帮助您在缩减成本的同时,提前交付丰富的桌面、Web和移动企业应用。
[*]Spread Studio 表格控件是一个功能和Excel类似的表格控件。用于在您的应用系统中实现表格数据录入和编辑等交互功能,并且提供灵活的定制能力和丰富的数据可视化效果。
[*]ActiveReports 是一款全方位的报表解决方案,它提供极易使用的报表设计器和三种报表模型,无需编码便可快速设计任意类型报表。
[*]Wijmo Enterprise 是为企业应用程序开发而推出的一系列包含HTML5和JavaScript的开发控件集。无论您应用程序是移动端、PC端、还是必须要支持IE6,Wijmo Enterprise均能满足您的需求。
页: [1]
查看完整版本: 详细解读AngularJS Providers