程序员人生 网站导航

AngularJS Providers 详解

栏目:htmlcss时间:2015-02-27 08:21:39
??

供应者(Providersimage

Each web application you build is composed of objects that collaborate to get stuff done. These objects need to be instantiated and wired together for the app to work. In Angular apps most of these objects are instantiated and wired together automatically by the injector service.

你创建的任何 Web 利用都是1些相互依赖的对象组合。这些对象需要被实例化并被绑定在1起工作。在 Angular 利用中,这些对象通过注入器服务自动完成实例化和绑定。

The injector creates two types of objects, services and specialized objects.

注入器创建了两种类型的对象,services(服务)和 specialized objects(特殊对象)。

Services are objects whose API is defined by the developer writing the service.

服务同等于对象,它的 API 由编写服务的开发者定义。

Specialized objects conform to a specific Angular framework API. These objects are one of controllers, directives, filters or animations.

特殊对象服从1套专门的 Angular 框架 API。这些对象是控制器、指令、过滤器或动画效果中的1个。

The injector needs to know how to create these objects. You tell it by registering a "recipe" for creating your object with the injector. There are five recipe types.

注入器需要知晓如何去创建这些对象。你通过注册1个“recipe(配方)” 来告知注入器去创建你的对象。共有5种类型的配方。

The most verbose, but also the most comprehensive one is a Provider recipe. The remaining four recipe types ― Value, Factory, Service and Constant ― are just syntactic sugar on top of a Provider recipe.

最繁琐,也是功能最全面的是 Provider recipe。剩下的4种类型――Value,Factory,Service 和 Constant ―― 仅仅是 Provider recipe 的语法糖。

Let's take a look at the different scenarios for creating and using services via various recipe types. We'll start with the simplest case possible where various places in your code need a shared string and we'll accomplish this via Value recipe.

接下来,我们看看如何在不同场景下通过不同的 recipe types 创建和使用 services 。我们将从最简单的例子开始,通过 Value recipe在代码中同享1个字符串。


 

Note: A Word on Modules

注意:模块概要

 

In order for the injector to know how to create and wire together all of these objects, it needs a registry of "recipes". Each recipe has an identifier of the object and the description of how to create this object.

为了让注入器知晓如何创建和绑定所有的对象,它需要1个"recipes"的注册表。每一个 recipe 都有唯1的对象标识符和和何创建这个对象的描写。

Each recipe belongs to an Angular module. An Angular module is a bag that holds one or more recipes. And since manually keeping track of module dependencies is no fun, a module can contain information about dependencies on other modules as well.

每一个 recipe 属于1个 Angular 模块。Angular 模块是1个保存了1个或多个 recipes 的袋子。手动跟踪模块依赖关系明显不是1个好方法,因此1个模块也能够包括依赖其它模块的相干信息。

When an Angular application starts with a given application module, Angular creates a new instance of injector, which in turn creates a registry of recipes as a union of all recipes defined in the core "ng" module, application module and its dependencies. The injector then consults the recipe registry when it needs to create an object for your application.

1个 Angular 利用开始于1个给定的利用模块时,Angular 会创建1个新的注入器实例,进而依照所有核心"ng"模块、利用模块和在它的依赖中统1定义的 recipes 来创建1个 recipes 的注册表。然后,注入器通过查询 recipes 注册表来创建利用所需的对象。

 

 

变量方式(Value Recipe

Let's say that we want to have a very simple service called "clientId" that provides a string representing an authentication id used for some remote API. You would define it like this:

假定我们想要取得1个非常简单的 service 叫做"clientId",它提供1个字符串用于表示某些远程 API 的认证 id。你可以这样去定义它:

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

Notice how we created an Angular module called myApp, and specified that this module definition contains a "recipe" for constructing the clientId service, which is a simple string in this case.

注意,我们创建1个名为 myApp 的 Angular 模块,然后指定了1个包括构建 clientId service 的配方,这只是1个字符串的简单例子。

And this is how you would display it via Angular's data-binding:

以下是如何通过 Angular数据绑定来显示它:

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

<html ng-app="myApp"> 
<body ng-controller="DemoController as demo"> 
Client ID: {{demo.clientId}} 
</body> 
</html>

In this example, we've used the Value recipe to define the value to provide when DemoController asks for the service with id "clientId".

在这个例子中,我们使用了 Value recipe 去定义这个 value,提供给 DemoController 要求这个服务的 id "clientId"。

On to more complex examples!

更复杂的例子!


 

工厂方式(Factory Recipe

The Value recipe is very simple to write, but lacks some important features we often need when creating services. Let's now look at the Value recipe's more powerful sibling, the Factory. The Factory recipe adds the following abilities:

这个 Value recipe 写起来非常简单,但缺少创建 service时常常用到的1些重要特点。现在让我们看看 Value recipe 更强大的兄弟――Factory。Factory recipe 增加了以下能力:

ability to use other services (have dependencies)

能够使用其它的 services(依赖)

service initialization

service 初始化

delayed/lazy initialization

延迟/怠惰初始化

The Factory recipe constructs a new service using a function with zero or more arguments (these are dependencies on other services). The return value of this function is the service instance created by this recipe.

Factory recipe通过1个包括有零个或多个参数(它所依赖的其它 services)的方法构造1个新的 service。这个方法的返回值是由 recipe 创建的这个服务的实例。

Note: All services in Angular are singletons. That means that the injector uses each recipe at most once to create the object. The injector then caches the reference for all future needs.

注意:Angular 中所有的服务都是单例模式。这意味着注入器创建这个对象时,仅使用1次recipe。然后注入器缓存所有将来需要的援用。

Since Factory is more powerful version of the Value recipe, you can construct the same service with it. Using our previous clientId Value recipe example, we can rewrite it as a Factory recipe like this:

由于 Factory 是Value recipe 更强大的版本,你可以构造和它1样的服务。使用我们之前的 clientId Value recipe 的例子,可以采取 Factory recipe 这样重写:

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

But given that the token is just a string literal, sticking with the Value recipe is still more appropriate as it makes the code easier to follow.

但斟酌到令牌仅仅是1个字符串常量,使用 Value recipe 更恰当,也更容易于代码的浏览。

Let's say, however, that we would also like to create a service that computes a token used for authentication against a remote API. This token will be called apiToken and will be computed based on the clientId value and a secret stored in the browser's local storage:

比如说,我们想创建1个用于计算远程 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; 
}]);

In the code above, we see how the apiToken service is defined via the Factory recipe that depends on clientId service. The factory service then uses NSA-proof encryption to produce an authentication token.

在上面的代码中,我们看到了如何通过工厂方法定义这个依赖于 clientId 服务的 apiToken 服务。这个工厂服务使用 NSA-proof 加密去产生1个认证令牌。

Note: It is best practice to name the factory functions as <serviceId>Factory (e.g. apiTokenFactory). While this naming convention is not required, it helps when navigating the code base or looking at stack traces in the debugger.

注意:工厂方法命名的最好实践类似于<serviceId>Factory (e.g. apiTokenFactory)。虽然这类命名习惯不是必须的,但它有助于代码库导航或查看调试器的堆栈跟踪。

Just like with Value recipe, Factory recipe can create a service of any type, whether it be a primitive, object literal, function, or even an instance of a custom type.

与 Value recipe 1样,Factory recipe 能够创建任何类型的服务,对象常量,方法,乃至1个自定义类型的实例。


 

 

服务方式(Service Recipe

JavaScript developers often use custom types to write object-oriented code. Let's explore how we could launch a unicorn into space via our unicornLauncher service which is an instance of a custom type:

JavaScript 开发者常常使用自定义的类型去编写面向对象的代码。让我们探究如何通过 unicornLauncher 服务发射1个 unicorn(独角兽)进入太空,这是1个自定义类型的实例:

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

We are now ready to launch unicorns, but notice that UnicornLauncher depends on our apiToken. We can satisfy this dependency on apiToken using the Factory recipe:

我们现在来准备发射 unicorn,但请注意 UnicornLauncher 依赖了我们的 apiToken。我们可使用 Factory recipe 来满足这个 apiToken的依赖:

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

This is, however, exactly the use-case that Service recipe is the most suitable for.

就是这样。不过,使用 Service recipe 才是最为恰当的例子。

The Service recipe produces a service just like the Value or Factory recipes, but it does so by invoking a constructor with the new operator. The constructor can take zero or more arguments, which represent dependencies needed by the instance of this type.

这个 Service recipe 产生1个类似于 Value 和 Factory recipes ,但它通过调用构造函数去履行 new 操作。这个构造函数可以接受零或多个参数,表示这个类型实例所需的依赖项。

Note: Service recipes follow a design pattern called constructor injection.

注意:Service recipes 的设计模式被称之为构造函数注入。

Since we already have a constructor for our UnicornLauncher type, we can replace the Factory recipe above with a Service recipe like this:

由于我们已有了1个 UnicornLauncher 类型的构造函数,就可以够像这样去用 Service recipe 替换 Factory recipe:

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

Much simpler!

多么简单!

Note: Yes, we have called one of our service recipes 'Service'. We regret this and know that we'll be somehow punished for our mis-deed. It's like we named one of our offspring 'Child'. Boy, that would mess with the teachers.

注意:是的,我们有1个被称为“Service”的 service recipes。很遗憾我们将由于 mis-deed 而遭到报应。这就像是给我们某1个后代起名叫“小盆友”。这么弄会让老师们困惑。

 

供应者方式(Provider Recipe

There are two more recipe types left to cover. They are both fairly specialized and are used infrequently. As already mentioned in the intro, the Provider recipe is the core recipe type and all the other recipe types are just syntactic sugar on top of it. It is the most verbose recipe with the most abilities, but for most services it's overkill.

还有两个 recipe 类型。它们都相当特殊,很少使用。如前述,Provider recipe 是核心 recipe 类型,所有其他的配方类型只是基于它的语法糖。它是最详细功能最多的 recipe,但对大多数服务来讲,它是过剩的。

Provider recipe is syntactically defined as a custom type that implements a $get method. This method is a factory function just like the one we use in Factory recipe. In fact, if you define a Factory recipe, an empty Provider type with the $get method set to your factory function is automatically created under the hood.

Provider recipe 是语法定义为1个自定义类型,实现 $get 的方法。这个方法是1个工厂方法,就像我们在 Factory recipe 中使用的1样。事实上,如果你定义1个 Factory recipe,钩子会自动创建1个包括空 Provider 类型 $get 方法的工厂方法。

You should use the Provider recipe only when you want to expose an API for application-wide configuration that must be made before the application starts. This is usually interesting only for reusable services whose behavior might need to vary slightly between applications.

只有当你希望1个利用程序配置的 API 必须在利用程序启动之前被创建,你才应当使用 Provider recipe 。通常只关注可重用服务的行动可能在利用程序间略有不同。

Let's say that our unicornLauncher service is so awesome that many apps use it. By default the launcher shoots unicorns into space without any protective shielding. But on some planets the atmosphere is so thick that we must wrap every unicorn in tinfoil before sending it on its intergalactic trip, otherwise they would burn while passing through the atmosphere. It would then be great if we could configure the launcher to use the tinfoil shielding for each launch in apps that need it. We can make it configurable like so:

我们假定 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); 
}]; 
});

To turn the tinfoil shielding on in our app, we need to create a config function via the module API and have theUnicornLauncherProvider injected into it:

为了在我们的利用程序中启用锡纸屏蔽罩,我们需要通过模块的 API 创建1个含有注入 UnicornLauncherProvider 的配置方法:

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

 

Notice that the unicorn provider is injected into the config function. This injection is done by a provider injector which is different from the regular instance injector, in that it instantiates and wires (injects) all provider instances only.

请注意 unicorn provider 被注入的配置方法,这类注入是通过 provider 的注入器完成的,不同于普通的实例注入器只是由 provider 实例进行实例化和绑定(注入)。

During application bootstrap, before Angular goes off creating all services, it configures and instantiates all providers. We call this the configuration phase of the application life-cycle. During this phase services aren't accessible because they haven't been created yet.

在利用程序启动期间,Angular 创建的所有服务前,配置和实例化所有的 providers。我们称之为利用程序生命周期中的配置阶段。在此阶段服务还不可用,由于它们还没有被创建。

Once the configuration phase is over, interaction with providers is disallowed and the process of creating services starts. We call this part of the application life-cycle the run phase.

配置阶段结束,providers 交互被制止,开始创建 services。我们称这1阶段为利用程序生命周期的运行阶段。


 

 

常量方式(Constant Recipe

We've just learned how Angular splits the life-cycle into configuration phase and run phase and how you can provide configuration to your application via the config function. Since the config function runs in the configuration phase when no services are available, it doesn't have access even to simple value objects created via Value recipe.
我们已学会了如何辨别利用程序生命周期中的配置阶段和运行阶段,如何通过配置方法向您的利用程序提供配置。由于配置方法运行在配置阶段,此时还没有服务可用,因此它不能访问任何对象,哪怕是通过 Value recipe 创建的 value 对象。

Since simple values, like url prefix, don't have dependencies or configuration, it is often handy to make them available in both the configuration and run phases. This is what the Constant recipe is for.

而简单的值,比如 url 的前缀,没有依赖或配置,需要在配置和运行阶段皆可以使用。这就是 Constant recipe。

Let's say that our unicornLauncher service can stamp a unicorn with the planet name it's being launched from if this name was provided during the configuration phase. The planet name is application specific and is used also by various controllers during the runtime of the application. We can then define the planet name as a constant like this:

假定 unicornLauncher 服务可以标出 unicorn 发射自哪颗行星,而且这个行星的名字需要在配置阶段提供。同时,星球的名字会由利用程序指定,并且被多个控制器在运行阶段使用。

We can then define the planet name as a constant like this:

我们可以依照以下方式定义这个星球的名字为1个常量:

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

We could then configure the unicornLauncherProvider like this:

我们这样可以配置 unicornLauncherProvider:

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

And since Constant recipe makes the value also available at runtime just like the Value recipe, we can also use it in our controller and template:

由于在运行阶段,Constant recipe 创建的值和 value recipe 1样,所以我们也能够在控制器和模板中使用它:

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

Earlier we mentioned that we also have special purpose objects that are different from services. These objects extend the framework as plugins and therefore must implement interfaces specified by Angular. These interfaces are Controller, Directive, Filter and Animation.

如上所述,我还有不同于 services,用于特殊目的对象。这些扩大作为框架的插件,因此必须实现 Angular 指定的接口。这些接口是:控制器、指令、过滤器和动画效果。

The instructions for the injector to create these special objects (with the exception of the Controller objects) use the Factory recipe behind the scenes.

举例说明注入器创建特殊对象(控制器对象除外)使用 Factory recipe。

Let's take a look at how we would create a very simple component via the directive api that depends on the planetNameconstant we've just defined and displays the planet name, in our case: "Planet Name: Greasy Giant".

让我们看1下如何通过指令 api 创建1个非常简单的组件,取决于我们刚才 planetName 定义的常量和行星的名字,在我们的例子中:“行星名称:Greasy Giant”(油腻伟人)。

Since the directives are registered via Factory recipe, we can use the same syntax as with factories.

自从通过工厂方法注册指令以后,我们就能够使用与 factory 相同的语法。

myApp.directive('myPlanet', ['planetName', function myPlanetDirectiveFactory(planetName) {
// directive definition object
return {
    restrict: 'E',
    scope: {},
    link: function($scope, $element) { $element.text('Planet: ' + planetName); }
}
}]);

We can then use the component like this:

然后,这样使用组件:

<html ng-app="myApp">
<body>
<my-planet></my-planet>
</body>
</html>

Using Factory recipes you can also define Angular's filters and animations, but the controllers are a bit special. You create a controller as a custom type that declares its dependencies as arguments for its constructor function. This constructor is then registered with a module. Let's take a look at the DemoController, created in one of the early examples:

使用 Factory recipes,你还可以定义 Angular 的过滤器和动画效果,但是控制器有些许特殊。创建1个控制器作为自定义类型,声明包括作为其依赖项参数的构造函数。然后注册这个构造函数到1个模块。让我们看看 DemoController 先前的例子:

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

The DemoController is instantiated via its constructor every time the app needs an instance of DemoController (in our simple app it's just once). So unlike services, controllers are not singletons. The constructor is called with all the requested services, in our case the clientId service.

DemoController 是根据利用程序的需要,通过其构造函数实例化的(在我们的简单利用中只有1次)。与服务不同,控制器其实不是单例的。构造函数被所有要求的服务调用,在我们的案例中是 clientId service。


 

 

结论(Conclusion

To wrap it up, let's summarize the most important points:

概括上述内容,让我来总结1下最重要的几点:

The injector uses recipes to create two types of objects: services and special purpose objects.

注入器通过使用 recipes 来创建两种类型的对象:服务和特殊目的对象。

There are five recipe types that define how to create objects: Value, Factory, Service, Provider and Constant.

1共有5种类型的 recipe 用于定义如何创建对象:变量、工厂、服务、供应者和常量。

Factory and Service are the most commonly used recipes. The only difference between them is that Service recipe works better for objects of custom type, while Factory can produce JavaScript primitives and functions.

工厂和服务是最经常使用的方式。二者唯一的不同是服务方式对自定义类型对象效果更好,而工厂方式可以提供 JavaScript 基元和方法。

The Provider recipe is the core recipe type and all the other ones are just syntactic sugar on it.

供应者方式是核心方式,所有其它方式都是它的语法糖。

Provider is the most complex recipe type. You don't need it unless you are building a reusable piece of code that needs global configuration.

供应者是最复杂的方式类型。除非你正在构建1段需要全局配置的可重用代码,否则不要使用它。

All special purpose objects except for Controller are defined via Factory recipes.

所有特殊目的对象都通过工厂方式来定义,除控制器。

Features / Recipe type

Factory

Service

Value

Constant

Provider

can have dependencies
支持依赖注入

yes

yes

no

no

yes

uses type friendly injection
使用友好的注入方式

no

yes

yes*

yes*

no

object available in config phase
配置阶段可用

no

no

no

yes

yes**

can create functions/primitives
可以创建方法/基元

yes

no

yes

yes

yes

* at the cost of eager initialization by using new operator directly

以使用 new 操作符初始化为代价。

** the service object is not available during the config phase, but the provider instance is (see the unicornLauncherProviderexample above).

** 服务对象在配置阶段不可用,除供应者实例(参见上面的 unicornLauncherProvider 示例)。

 

原文链接: https://docs.angularjs.org/guide/providers

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐