caoruiy‘s blog

Wisdom outweighs any wealth

Angular开发者指南-Providers(服务)

原文:Developer Guide/Providers

Providers

您构建的每个Web应用程序都由协作完成工作的对象组成。这些对象需要被实例化并连接在一起才能使应用程序工作。在AngularJS应用程序中,大多数这些对象都被实例化,并由注射器服务( injector service )自动连接在一起。

注射器创建两种类型的对象:服务(services)和专用对象(specialized objects)。

服务是由开发人员自己编写,定义其API的对象。

专用对象符合特定的AngularJS框架API。这些对象是控制器(controller),指令(directive),过滤器(filter)或动画(animation)之一。

注射器需要知道如何去创建这些对象。您使用注射器,并告诉他创建您的对象所需要的“recipe(配方)”来注册。有五种配方类型:

最详尽的,也是最全面的一个是 Provider recipe。剩下的 recipe 类型:Value, Factory, Service, Constant。都只是 Provider recipe 的语法糖。

下面就来看一下,在不同场景下创建和使用不同recipe的方法。先从最简单的 Value recipe 开始。

Note: A Word on Modules

为了让注册器知道如何创建一个对象并指导如何把所有的都想连接起来,每一个对象都有一个标识和关于如何创建他的描述。

每一个 recipe 都要属于一个 Angular.module 模块 ,module就像是一个装着他们的袋子,而且,由于手动跟踪模块依赖关系并不是很好的做法,因此module还可以声明有关其他模块依赖关系的信息。

如果一个Angular应用根据给定的module启动,AngularJS 会创建一个 injector 的实例,它又创建了一个 recipe 的注册表,作为核心“ng”模块,应用程序模块及其依赖关系中定义的所有 recipe 的联合。然后,当需要为应用程序创建一个对象时,注射器会查询 recipe 注册表。

Value Recipe

假设我们想要一个名为“clientId”的简单服务,它提供一个表示用于某些远程API的身份验证ID的字符串。你可以这样定义它:

var app = angular.module('app', []);
app.value('clientId', 'a12345654321x');

只包含一个字符串的服务创建好之后,你可以这样去使用它:

app.controller('ctrl', ['clientId', function(clientId){
    this.clientId = clientId;
}])

在HTML页面中,就可以引用到:

html
    head
        meta(charset="utf-8")
    body('ng-app'='app')
        div('ng-controller'='ctrl')
            div {{clientId}}

value 服务是所有服务中最简单的。

Factory Recipe

Value recipe 非常简单,但缺少创建服务时常常需要的一些重要功能。Factory recipe 比 Value recipe 要强大一些。Factory recipe 增加了以下能力:
1. 可以依赖于其他服务
2. 服务初始化
3. 推迟/惰性 初始化
Factory recipe 使用一个可以包含任意参数(依赖的其他服务)的函数来构建一个服务。而函数的返回值是由此 recipe 创建的服务实例。

需要注意的是:Angular中所有服务都是独立的。这就意味着每一个 recipe 都只能使用一次。然后Angular会把他们缓存起来。

Factory recipe 是 Value recipe 的一个升级版,可以用它构建相同的服务,使用我们以前的 clientId Value recipe 示例,我们可以将其重写为 Factory recipe,如下所示:

app.factory('clientId', function(){
    return 'a12345654321x';
});

但是鉴于令牌只是一个字符串文字,跟应该使用 Value recipe,因为它使代码更容易遵循。

不过,我们假设我们还要创建一个服务,它计算一个用于根据远程API进行身份验证的令牌。该令牌将被称为apiToken,并将基于clientId值和存储在浏览器本地存储中的秘钥来计算:

app.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;
}]);

在上面的代码中,我们看到apiToken服务是如何依赖clientId服务并通过 Factory recipe 定义的。然后,工厂服务使用NSA防护加密来产生认证令牌。

最佳实践:将工厂命名为Factory(例如,apiTokenFactory)。虽然此命名约定不是必需的,但在追踪代码库或查看调试器中的堆栈跟踪时有帮助。

就像使用Value recipe一样,Factory recipe可以创建任何类型的服务,无论是原始的,对象文字的,功能的,甚至是自定义类型的实例。

Service Recipe

JavaScript开发人员经常使用自定义类型来编写面向对象的代码。我们来探讨我们如何通过我们的unicornLauncher服务(一种自定义类型的实例)来将独角兽发射到太空中(代码的语义上,是发射一只独角兽到太空中,外国老外写文章都希望俏皮一点,显得文章不那么死板):

function UnicornLauncher(apiToken) {

  this.launchedCount = 0;
  this.launch = function() {
    // Make a request to the remote API and include the apiToken
    ...
    this.launchedCount++;
  }
}

我们现在准备发射独角兽,但注意到UnicornLauncher依赖于我们的apiToken。我们可以使用 factory recipe 来满足对apiToken的依赖:

app.factory('unicornLauncher', ["apiToken", function(apiToken) {
  return new UnicornLauncher(apiToken);
}]);

然而,这正是 Service recipe 最适合的用例。

服务配方像Value或Factory配方一样生成一个服务,但是通过使用new运算符调用构造函数来实现。构造函数可以取零个或多个参数,这些参数表示此类型的实例所需的依赖关系。

注意:Service recipe 遵循称为构造函数注入的设计模式。

由于我们已经有了UnicornLauncher类型的构造函数,所以我们可以用以下这样的服务配方来替换上面的 Factory recipe:

app.service('unicornLauncher', ["apiToken", UnicornLauncher]);

译者标注:上面的写法如果不理解,他和下面的代码是等效的:

app.service('unicornLauncher', ["apiToken", function(apiToken) {

  this.launchedCount = 0;
  this.launch = function() {
  // Make a request to the remote API and include the apiToken
  // ...
  this.launchedCount++;
  }
}]);

Provider Recipe

如介绍中已经提到的,Provider recipe 是核心 recipe 类型,所有其他 recipe 类型只是其上的语法糖。它是具有最多能力的最详尽的食谱,但对于大多数服务来说,它是过度的(译注:也可以说是过度设计的,增加了使用的复杂度)。

提供者配方在语法上定义为实现$ get方法的自定义类型。这种方法是一个工厂函数,就像我们在 factory recpe 中使用的一样。实际上,如果定义工厂配方,则会在引擎盖下自动创建一个空值为$ get方法设置为工厂函数的Provider类型。

只有当您要暴露一组在应用程序开始之前必须进行的应用程序范围配置的API时,才应使用 Provider recipe。这通常只对可重用的服务有用,这些服务的行为可能需要在应用程序之间略有不同。

让我们说,我们的unicornLauncher服务非常棒,许多应用程序都使用它。默认情况下,发射器将独角兽射入太空,无任何保护屏障。但是在一些行星上大气如此之厚,所以在把它发射到太空之前,我们必须把他设置保护罩。那么如果我们可以配置启动器,在需要它的应用程序中为每个启动使用保护屏障,那将是很有用的。我们可以这样配置:

app.provider('unicornLauncher', function UnicornLauncherProvider() {
  var useTinfoilShielding = false;

  this.useTinfoilShielding = function(value) {
    useTinfoilShielding = !!value;
  };

  this.$get = ["apiToken", function unicornLauncherFactory(apiToken) {

    // 我们假设UnicornLauncher的构造函数也被改为     
    // 接受并使用useTinfoilShielding参数
    return new UnicornLauncher(apiToken, useTinfoilShielding);
  }];
});

要在我们的应用程序中打开保护罩,我们需要通过模块API创建一个配置函数,并将UnicornLauncherProvider注入:

app.config(["unicornLauncherProvider", function(unicornLauncherProvider) {
    unicornLauncherProvider.useTinfoilShielding(true);
}]);

译注:上面虽然有很详细的教程,当时还是有很多的细节需要注意:
1. 如何设置配置函数
在 provider 中,挂载在 provider recipe 下的方法和属性可以用来在 app.config 方法中做配置方法和属性使用。例如上例中的 this.useTinfoilShielding 方法。
2. 如何定义依赖,例如上例中,在 this.$get() 方法的定义中,声明依赖,同时在实例化中写入依赖的模块,在构造函数中也需要引入依赖。
3. 如何配置使用
app.config() 函数中,引入需要配置的 provider 但是配置的 provider 需要在服务名之后加上 Provider, 例如上面的 unicornLauncher 服务需要写成 unicornLauncherProvider

下面由译者为大家提供一个完整的实例,其作用就是一个用来显示你的名字的服务。

// 编写服务
// service.js
angular.module('service', [])
.value('defaultName', 'admin')
.provider('yourName',function(){
    var preverName = '';
    // 以下方法和变量可以在配置参数中使用
    this.setYourName = function(name){
        preverName = name;
    } 
    this.sex = 'man';
    // 具体实现
    function yourName(defaultName,that){
        this.name = preverName || defaultName;
        this.sex = that.sex;
    }
    this.$get = ['defaultName', function(defaultName){
        // 实例化时,引入依赖的服务和本函数内的共享变量this
        return new yourName(defaultName, this);
    }];
})

使用上面的服务,显示你的名字,默认的名字是 admin, 性别是 man,你也可以自定义

// app.js
// 引入依赖 service
angular.module('app',['service'])
.config(['yourNameProvider', function(yourName){
    yourName.setYourName('root');// 设置你的名字,如果设成 false 则使用 defaultName 服务定义的默认名字 admin
    yourName.sex = 'female';// 不定义,则使用默认的 man
}])
.controller('ctrl',['yourName', function(yourName){
    console.log(yourName.name);// root
    console.log(yourName.sex);// female
}])

请注意,独角兽提供程序被注入到配置函数中。这种注射由提供者注射器完成,该注射器与常规实例注射器不同,因为它仅实例化和连接(注入)所有提供者实例。

在应用程序引导期间,在AngularJS关闭创建所有服务之前,它会配置和实例化所有提供程序。我们将其称为应用程序生命周期的配置阶段。在此阶段,服务无法访问,因为尚未创建。 一旦配置阶段结束,不允许与提供商的交互,并且创建服务的过程开始。我们将应用程序生命周期的这一部分称为运行阶段。

Constant Recipe(常数配方/常数服务)

我们刚刚了解了AngularJS如何将生命周期分解到配置阶段和运行阶段,以及如何通过配置功能为应用程序提供配置。由于配置函数在没有可用服务的配置阶段运行,所以即使是通过 Value recipe 创建的简单值对象也无法访问。

由于简单的值(如URL前缀)没有依赖关系或配置,因此在配置和运行阶段都可以使用它们。这是 constant recipe 提供的功能。

假设我们的unicornLauncher服务可以在配置阶段提供这个名称之前,将独角兽与它所发射的行星名称进行印记。行星名称是应用程序特定的,并且在应用程序的运行时期间也被各种控制器使用。然后,我们可以将行星名称定义为常量:

app.constant('planetName', 'Greasy Giant');

我们可以这样配置unicornLauncherProvider:

app.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) {
  unicornLauncherProvider.useTinfoilShielding(true);
  unicornLauncherProvider.stampText(planetName);
}]);

而且由于 constant recipe 在运行时也可以使用Value值,因此我们也可以在控制器和模板中使用它:

app.controller('DemoController', ["clientId", "planetName", function(clientId, planetName) {
  this.clientId = clientId;
  this.planetName = planetName;
}]);

Special Purpose Objects(特殊用途对象)

之前我们提到我们也有不同于服务的特殊用途对象。这些对象用插件的形式来扩展angular框架本身,因此必须实现AngularJS指定的接口。这些接口是Controller,Directive,Filter和Animation。

注射器在后台使用 Factory recipe 创建这些特殊对象的说明(Controller对象除外)。

我们来看看我们将如何通过指令api来创建一个非常简单的组件,该指令依赖于我们刚刚定义的planetName常量,并在我们的例子中显示行星名称:“Planet Name:Greasy Giant”。

由于通过Factory recipe注册指令,所以我们可以使用与工厂相同的语法。

myApp.directive('myPlanet', ['planetName', function(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>

使用工厂配方,您还可以定义AngularJS的过滤器和动画,但控制器有点特别。您创建一个控制器作为自定义类型,将其依赖项声明为其构造函数的参数。然后,该构造函数向模块注册。我们来看看DemoController,它是在早期的例子中创建的:

app.controller('DemoController', ['clientId', function(clientId) {
  this.clientId = clientId;
}]);

DemoController通过其构造函数实例化,每次应用程序需要一个DemoController的实例(在我们简单的应用程序中只有一次)。所以不像服务,控制器不是独立的。在所有请求的服务中调用构造函数,在我们的例子中是clientId服务。

Conclusion

总结一下最重要的一点:
1. 注射器使用recipe来创建两种类型的对象:服务和特殊用途对象
2. 一共有五种recipe方式来创建对象:value, factory, service, provider, constant
3. Factory 和 service 是最常使用的服务,它们之间的唯一区别是:service对于自定义类型的对象更好,而Factory可以生成JavaScript原语和函数。
4. Provider recipe 是核心 recipe,所有其他 recipe 都扩展自 provider
5. Provider 是最复杂的。 recipe,你不需要使用它,除非你需要构建一个依赖全局配置的可重用的服务。
6. 特殊服务对象除了controller都是使用Factory recipe 创建的。

译者注:经过上面的文章,可以很好的明白Angular中的服务。

下面在给出他们创建的实例

angular.module('service',[])
// 值服务
.value('demoValue','demoValueRecipe')
// 常亮服务
.constant('demoComstant','demoCom')
// 工厂服务
.factory('demoFactory',function(){
    var someThing;

    return someThing;
})
// 服务服务
.service('demoService', function(){
    this.funcA = ()=>{}
    this.paramA = '';
})
// 提供者服务
.provider('demoProvider', function(){
    // config
    this.funcA = ()=>{}
    this.paramA = '';

    // 服务原型-存在依赖
    vat providerFunc = function(dependence, that){

    }
    // 服务
    this.$get = ['dependence', function(dependence){
        return new providerFunc(dependence, this);
    }]
})
点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注