AngularJS学习:Providers

原本地址:https://docs.angularjs.org/guide/providers

Providers

Each webapplication you build is composed of objects that collaborate to get stuffdone. These objects need to be instantiated and wired together for the app towork. In AngularJS apps most of these objects are instantiated and wiredtogether automatically by the injector service.

The injectorcreates two types of objects, services and specializedobjects.

Services areobjects whose API is defined by the developer writing the service.

Specializedobjects conform to a specific AngularJS framework API. These objects are one ofcontrollers, directives, filters or animations.

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

The most verbose,but also the most comprehensive one is a Provider recipe. The remaining fourrecipe types — Value, Factory, Service and Constant — are just syntacticsugar on top of a provider recipe.

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

Note: A Word onModules

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

Each recipebelongs to an AngularJS module. An AngularJSmodule is a bag that holds one or more recipes. And since manually keepingtrack of module dependencies is no fun, a module can contain information aboutdependencies on other modules as well.

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

Value Recipe

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

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

myApp.value('clientId', 'a12345654321x');

Notice how wecreated an AngularJS module called myApp, and specifiedthat this module definition contains a "recipe" for constructingthe clientId service, which is a simple string inthis case.

And this is howyou would display it via AngularJS's data-binding:

myApp.controller('DemoController', ['clientId', function DemoController(clientId) {

  this.clientId = clientId;

}]);

<htmlng-app="myApp">

  <bodyng-controller="DemoControlleras 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".

On to morecomplex examples!

Factory Recipe

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

这个Value食谱是非常简单地去编写,但是缺乏一些重要地特征,当我们创建service的时候我们经常需要的重要特征。让我们现在看看这个Value食谱更强大的同胞,Factory。这个Factory食谱增加了以下的能力。

  • ability to use other services (have dependencies)
  • 使用其它service的能力(具有依赖)。
  • service initialization
  • service初始化。
  • delayed/lazy initialization
  • 延迟/懒 初始化。

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

Factory食谱构造一个新的service,使用一个方法,采用零个或者多个参数(这些是对于其它service的依赖)。这个方法返回的值是被这个食谱创建的service实例。

Note: Allservices in AngularJS are singletons. That means that the injector uses eachrecipe at most once to create the object. The injector then caches thereference for all future needs.

注意:所以serviceAngularJS中是单例的。这意味着注入器使用每个食谱最多一次去创建对象。这个注入器然后为了所有未来的需求去缓存对于这个对象的引用。

Since a Factoryis a more powerful version of the Value recipe, the same service can beconstructed with it. Using our previous clientIdValue recipe example,we can rewrite it as a Factory recipe like this:

因为一个Factory是一个更有力的Value食谱的版本,同样的service可以被它构造,使用我们之前的clientId Value食谱的例子,我们可以重写它作为Factory食谱就像这样:

myApp.factory('clientId', functionclientIdFactory() {

  return 'a12345654321x';

});

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

但是鉴于这个token仅仅是一个字符串,坚持使用Value食谱是更合适的,因为它让代码更容易去跟踪。

Let's say,however, that we would also like to create a service that computes a token usedfor 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 localstorage:

比如说,然后,我们也想要创建一个service,可以计算被用于针对一个远程API的认证的token。这个token将会被叫做apiToken并且将会被计算,基于这个clientId 值,和一个储存在浏览器本地存储中的密钥。

myApp.factory('apiToken', ['clientId', functionapiTokenFactory(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 codeabove, we see how the apiToken service isdefined via the Factory recipe that depends on the clientId service. The factory service then uses NSA-proofencryption to produce an authentication token.

在上面的代码中,我们看到一个apiToken service是如何被定义,通过依赖clientId serviceFactory食谱。这个工厂service然后使用NSA-proof 加密算法去生成一个认证的token

Best Practice: name thefactory functions as  <serviceId>Factory (e.g., apiTokenFactory). While this namingconvention is not required, it helps when navigating the code base or lookingat stack traces in the debugger.

最好的练习:把工厂函数命名成类似<serviceId>Factory(例如apiTokenFactory)。虽然这不是必须的,但是在浏览代码库和查看调试器中的堆栈跟踪时有帮助。

Just like withthe Value recipe, the Factory recipe can create a service of any type, whetherit be a primitive, object literal, function, or even an instance of a customtype.

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

Service Recipe

JavaScriptdevelopers often use custom types to write object-oriented code. Let's explorehow we could launch a unicorn into space via our unicornLauncher servicewhich is an instance of a custom type:

JavaScript开发者经常使用自定义的类型去编写面向对象的代码。让我们浏览我们是如何能够发射一个独角兽去太空中去,通过我们的unicornLauncherservice,这是一个自定义类型的实例。

function UnicornLauncher(apiToken) {

 

  this.launchedCount = 0;

  this.launch = function() {

    // Make a request to the remote API andinclude the apiToken

    ...

    this.launchedCount++;

  }

}

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

我们现在准备好去发射独角兽,但是注意到这个UnicornLauncher依赖我们的apiToken。我们可以满足这个对于apiToken的依赖,通过使用Factory食谱。

myApp.factory('unicornLauncher', ["apiToken",function(apiToken) {

  return new UnicornLauncher(apiToken);

}]);

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

这个,然后,正好是Service食谱最适合使用的用例。

The Servicerecipe produces a service just like the Value or Factory recipes, but it doesso by invoking a constructor with the newoperator. Theconstructor can take zero or more arguments, which represent dependenciesneeded by the instance of this type.

Service食谱生产一个service,就像Value或者Factory食谱一样,但是它是通过调用一个带有new操作符的构造器来做到这点的。这个构造器可以采用零个或者多个参数,这些参数代表这这个类型的实例所需要的依赖。

Note: Servicerecipes follow a design pattern called constructorinjection.

注意:Service食谱跟随者一个设计模式,叫做构造注入。

Since we alreadyhave a constructor for our UnicornLauncher type, we can replace the Factoryrecipe above with a Service recipe like this:

因为我们已经有一个关于我们的UnicornLauncher类型的构造器,我们可以使用Service食谱就像以下这样去代替Factory食谱。

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

Much simpler!

更加简单。(哪里有new呢?)

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

注意:是的,我们已经把我们service食谱的一个称作“Service”。我们对此很抱歉,并且知道我们将会多少收到惩罚为我们的罪行。这就像我们把我们的后代中的一个命名为“Child”。孩子,那会让老师混乱的。

Provider Recipe

As alreadymentioned in the intro, the Provider recipe is the core recipe type and all theother recipe types are just syntactic sugar on top of it. It is the most verboserecipe with the most abilities, but for most services it's overkill.

就像在简介中提到的,Provider食谱是核心食谱类型,并且所有其它的食谱类型都只是它上面的句法糖。它是最详尽的食谱,伴随着最多的能力,但是对大多数service来说,它的杀伤力太大了。

The Providerrecipe is syntactically defined as a custom type that implements a $get method. This method is a factory function just like the one we usein the Factory recipe. In fact, if you define a Factory recipe, an emptyProvider type with the $get method setto your factory function is automatically created under the hood.

Provider的食谱是被句法化地定义,作为一个自定义的类型,来实现一个$get方法。这个方法是一个工厂方法,就像我们在Factory食谱中用的那个一样。如果你定义了一个Factory的食谱,设置到你的工厂方法的一个空的Provider类型(伴随着$get方法)是潜在地,自动创建的。

You should usethe Provider recipe only when you want to expose an API for application-wideconfiguration that must be made before the application starts. This is usuallyinteresting only for reusable services whose behavior might need to varyslightly between applications.

你应该使用Provider食谱,仅在你想要暴露一个API为应用级范围的配置(要在应用开始前完成的)。这通常有意思,仅仅为了可重用的services,表现可能需要在应用中有轻微的不同。

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

比如说,我们的unicornLauncher 服务是这么令人了不起,好多应用都用它。默认,这个发射器会把独角兽发射到太空,而没有任何的保护层。但是在一些请求上,大气是这样的厚以至于我们必须把独角兽包裹在锡纸中,在送它去进行它的银河间的旅行之前,否则他们将会燃烧,当穿过大气的时候。我们如果能配置这个发射器去使用锡纸防护对一次应用中需要的发射那将是很棒的。我们将这样配置它:

myApp.provider('unicornLauncher', function UnicornLauncherProvider() {

  var useTinfoilShielding = false;

 

  this.useTinfoilShielding = function(value) {

    useTinfoilShielding = !!value;

  };

 

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

 

    // let's assume that the UnicornLauncherconstructor was also changed to

    // accept and use theuseTinfoilShielding argument

    return new UnicornLauncher(apiToken,useTinfoilShielding);

  }];

});

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

myApp.config(["unicornLauncherProvider",function(unicornLauncherProvider) {

 unicornLauncherProvider.useTinfoilShielding(true);

}]);

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

注意这个独角兽provider是被注入到config函数的。这个注入是被一个provider注射器完成,这不同于常规的实例注入器,它仅仅实例化和装配所有的provider实例。

Duringapplication bootstrap, before AngularJS goes off creating all services, itconfigures and instantiates all providers. We call this the configuration phaseof the application life-cycle. During this phase, services aren't accessiblebecause they haven't been created yet.

在应用自举阶段,在AngularJS 开始创建所有的services之前,它配置并且实例化所有的providers。我们称应用生命周期的这个阶段为配置阶段。在这个阶段,services不被访问,因为它们还没有被创建。

Once theconfiguration phase is over, interaction with providers is disallowed and theprocess of creating services starts. We call this part of the applicationlife-cycle the run phase.

一旦这个配置阶段结束,和providers的互动将不被允许并且创建services的过程开始。我们称应用生命周期的这部分为运行阶段。

Constant Recipe

We've justlearned how AngularJS splits the life-cycle into configuration phase and runphase and how you can provide configuration to your application via the configfunction. Since the config function runs in the configuration phase when noservices are available, it doesn't have access even to simple value objectscreated via the Value recipe.

Since simplevalues, like URL prefixes, don't have dependencies or configuration, it's oftenhandy to make them available in both the configuration and run phases. This iswhat the Constant recipe is for.

Let's say thatour unicornLauncher servicecan stamp a unicorn with the planet name it's being launched from if this namewas provided during the configuration phase. The planet name is applicationspecific and is used also by various controllers during the runtime of theapplication. We can then define the planet name as a constant like this:

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

We could thenconfigure the unicornLauncherProvider like this:

myApp.config(['unicornLauncherProvider', 'planetName',function(unicornLauncherProvider, planetName) {

 unicornLauncherProvider.useTinfoilShielding(true);

 unicornLauncherProvider.stampText(planetName);

}]);

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

myApp.controller('DemoController', ["clientId", "planetName", function DemoController(clientId,planetName) {

  this.clientId = clientId;

  this.planetName = planetName;

}]);

<htmlng-app="myApp">

  <bodyng-controller="DemoControlleras demo">

   Client ID: {{demo.clientId}}

   <br>

   Planet Name: {{demo.planetName}}

  </body>

</html>

Special PurposeObjects

Earlier wementioned that we also have special purpose objects that are different fromservices. These objects extend the framework as plugins and therefore mustimplement interfaces specified by AngularJS. These interfaces are Controller,Directive, Filter and Animation.

The instructionsfor the injector to create these special objects (with the exception of theController objects) use the Factory recipe behind the scenes.

Let's take alook at how we would create a very simple component via the directive api thatdepends on the planetName constantwe've just defined and displays the planet name, in our case: "PlanetName: Greasy Giant".

Since thedirectives are registered via the Factory recipe, we can use the same syntax aswith factories.

myApp.directive('myPlanet', ['planetName', functionmyPlanetDirectiveFactory(planetName) {

  // directive definition object

  return {

    restrict: 'E',

    scope: {},

    link: function($scope, $element) {$element.text('Planet: ' + planetName); }

  }

}]);

We can then usethe component like this:

<htmlng-app="myApp">

  <body>

   <my-planet></my-planet>

  </body>

</html>

Using Factoryrecipes, you can also define AngularJS's filters and animations, but thecontrollers are a bit special. You create a controller as a custom type thatdeclares its dependencies as arguments for its constructor function. Thisconstructor is then registered with a module. Let's take a look at the DemoController, created in one of the early examples:

myApp.controller('DemoController', ['clientId', function DemoController(clientId) {

  this.clientId = clientId;

}]);

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

Conclusion

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

  • The injector uses recipes to create two types of objects: services and special purpose objects
  • There are five recipe types that define how to create objects: Value, Factory, Service, Provider and Constant.
  • Factory and Service are the most commonly used recipes. The only difference between them is that the Service recipe works better for objects of a custom type, while the Factory can produce JavaScript primitives and functions.
  • 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.
  • All special purpose objects except for the 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

yes

yes

yes

yes

yes

can create primitives

yes

no

yes

yes

yes

* at the cost ofeager initialization by using new operatordirectly

** the serviceobject is not available during the config phase, but the provider instance is(see the unicornLauncherProvider exampleabove).

 

posted on 2017-04-06 21:36  chaiyu2002  阅读(193)  评论(0编辑  收藏  举报

导航