欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

AngularJS中的factory、service以及provider的区别 有大用 有大大用

AngularJS中的factory、service以及provider的区别

初学 AngularJS 时, 肯定会对其提供 factory 、 service 和 provider 感到疑惑, 这三种都是提供服务的方式, 他们到底有什么区别呢?

factory

factory 可以认为是设计模式中的工厂方法, 就是你提供一个方法, 该方法返回一个对象的实例, 对于 AngularJS 的 factory 来说, 就是先定义一个对象, 给这个对象添加属性和方法, 然后返回这个对象, 例如:


[javascript] view plain copy
  1. var app = angular.module('MyApp', []);  

  2.   

  3. app.factory('MyFactory'function() {  

  4.     // define result object of factory.  

  5.     var result = {};  

  6.     // add some property and method to the object  

  7.     result.greeting = 'Hello from factory.';  

  8.     // return the object;  

  9.     return result;  

  10. });  


最后 controller 拿到的就是 result 对象, 相当于下面的代码:


[javascript] view plain copy
  1. var factoryResult = MyFactory();  


所谓的 factory 就是这么简单

service


service 通过 new 运算符进行实例化, 可以认为是一个类型, 只要把属性和方法添加到 this 对象上即可, 不用显式返回什么对象, 比如下面的代码:


[javascript] view plain copy
  1. app.service('MyService'function() {  

  2.     this.greeting = 'Hello from service';  

  3. });  


controller 拿到的对象就是上面代码中 this 指向的对象, 相当于下面的代码:


[javascript] view plain copy
  1. var serviceObj = new MyService();  


provider

与 factory 和 service 稍有不同的是, provider 必须提供一个 $get 方法, $get 方法和 factory 要求是一致的, 即: 先定义一个对象, 给这个对象添加属性和方法, 然后返回这个对象, 例如:

[javascript] view plain copy
  1. app.provider('MyProvider'function() {  

  2.       

  3.     this.$get = function() {  

  4.         var result = {};  

  5.         result.greeting = 'Hello from provider';  

  6.         return result;  

  7.     }  

  8. })  


最后 controller 拿到的对象就是 provider 的 $get 方法返回的对象, 相当于下面的代码:

[javascript] view plain copy
  1. var instance = new MyProvider();  

  2. var provider = instance.$get();  



使用 factory、 service 与 provider


factory、 service 与 provider 使用起来是一样的, 都是通过 AngularJS 的依赖注入使用, 比如:

[javascript] view plain copy
  1. // inject factory, service and provider to a controller  

  2. app.controller('TestController', ['$scope''MyFactory''MyService''MyProvider'function($scope, myFactory, myService, myProvider) {  

  3.     $scope.greetingFromFactory = myFactory.greeting;  

  4.     $scope.greetingFromService = myService.greeting;  

  5.     $scope.greetingFromProvider = myProvider.greeting;  

  6. }]);  


对应的 HTML 视图为:

[html] view plain copy
  1. <body ng-controller="TestController">  

  2.     <p>greeting from factory: {{greetingFromFactory}} </p>  

  3.     <p>greeting from service: {{greetingFromService}} </p>  

  4.     <p>greeting from provider: {{greetingFromProvider}} </p>  

  5. </body>  



provider 可以在应用启动时进行配置


provider 的特殊之处就是可以在 module 启动时进行配置, 从而达到特殊的用途, 比如在上面的 provider 中可以添加一个 setName 方法, 可以在启动时调用这个方法, 进行一些额外的初始化工作:

[javascript] view plain copy
  1. app.provider('MyProvider'function() {  

  2.     // default name is 'anonymous';  

  3.     var defaultName = 'anonymous';  

  4.     var name = defaultName;  

  5.     // setName can be called duaring module init  

  6.     this.setName = function(newName) {  

  7.         name = newName;  

  8.     }  

  9.       

  10.     this.$get = function() {  

  11.         var result = {};  

  12.         result.greeting = 'Hello from provider';    

  13.         result.name = name;  

  14.         result.defaultName = defaultName;  

  15.         return result;  

  16.     }  

  17. })  

添加了 setName 方法之后, 可以 module 启动时来调用这个方法, 实现对 provider 的配置

[javascript] view plain copy
  1. app.config(function(MyProviderProvider) {  

  2.     MyProviderProvider.setName('Angularjs Provider');  

  3. });  

在 controller 中添加显示 provider 的这些信息:


[html] view plain copy
  1. app.controller('TestController', ['$scope', 'MyFactory', 'MyService', 'MyProvider', function($scope, myFactory, myService, myProvider) {  

  2.     $scope.greetingFromFactory = myFactory.greeting;  

  3.     $scope.greetingFromService = myService.greeting;  

  4.     $scope.greetingFromProvider = myProvider.greeting;  

  5.       

  6.     $scope.defaultName = myProvider.defaultName;  

  7.     $scope.name = myProvider.name  

  8. }]);  


对应的 HTML 视图也调整一下


[html] view plain copy
  1. <body ng-controller="TestController">  

  2.     <p>greeting from factory: {{greetingFromFactory}} </p>  

  3.     <p>greeting from service: {{greetingFromService}}</p>  

  4.     <p>greeting from provider: {{greetingFromProvider}} </p>  

  5.     <p>defaultName: {{defaultName}}, config to: {{name}} </p>  

  6. </body>  


最后程序运行截图如下:


Angularjs 中的 provider, factory 和 service

ZheX.me  85 阅读

相信开发过 angularjs 的兄弟们都会被 provider, factory 和 service 困扰,有没有?本人开始也是一头雾水,看似都是差不多的东西,各有什么用呢?经过去外国社区大量搜索以后终于找到了答案:

其实 provider, factory, service 都属于 provider, 对的,你没有听错。难道 google 的那帮大牛疯了,没事搞3个一样东西出来?还是让我们先来看看代码分析吧。

什么是 provider ?

provider 可以为应用提供通用的服务,形式可以是常量,也可以是对象。

比如我们在 controller 里注入进来的 $http, $scope 都可以认为是 provider。

app.controller('MainCtrl', function ($scope, $http) {
  $http.get(....).then(.....);
}

现在让我们自己来定制一个 provider

// 方法 1
$provide.provider('test', {
  n: 3;
  $get: function () { 
    return n;
  };
});

// 方法 2
$provide.provider('test', function () {
  this.n = 3;
  this.$get = function () { 
    return n;
  };
});

// 使用
app.controller('MainCtrl', function ($scope, test) {
  $scope.test = test;
});

让我们看看 provider 的内部实现代码

function provider(name, provider_) {
  if (isFunction(provider_)) {
    provider_ = providerInjector.instantiate(provider_);
  }
  if (!provider_.$get) {
    throw Error('Provider ' + name + ' must define $get factory method.');
  }
  return providerCache[name + providerSuffix] = provider_;
}

可以看到 provider 的基本原则就是通过实现 $get 方法来进行单例注入,使用时获得的就是 $get 执行后的结果。

factory

那如果每次都要写一个 $get 是不是很麻烦? OK,所以我们有了 factory。 factory 可以说是 provider 的变种, 方法中的第二个参数就是 $get 中的内容。

// 定义 factory 
$provide.factory('dd', function () { 
  return new Date();
});

// 使用
app.controller('MainCtrl', function ($scope, dd) {
  $scope.mydate = dd;
});

factory 的实现源代码:

function factory(name, factoryFn) { 
  return provider(name, { 
    $get: factoryFn 
  }); 
}

service

在 factory 的例子中我们还是需要 new 一个对象返回,而 service 就更简单了,这一步都帮你省了, 他的第二个参数就是你要返回的对象类, 也就是 new 的哦给你工作都不用你做了。够清爽吧?

// 定义 service 
$provide.service('dd', Date);

下面是 service 的实现源代码:

function service(name, constructor) {
  return factory(name, ['$injector', function($injector) {
    return $injector.instantiate(constructor);
  }]);
}

然后 factory 和 service 带来代码精简的同时也损失了一些特性。 provider 定义的服务是可以通过模块 config 来配置的。

相信开发过 angularjs 的兄弟们都会被 provider, factory 和 service 困扰,有没有?本人开始也是一头雾水,看似都是差不多的东西,各有什么用呢?经过去外国社区大量搜索以后终于找到了答案:

其实 provider, factory

来自  http://ju.outofmemory.cn/entry/291586


Angular注册Provider 

前端笔记
  前端笔记
 发布于 2017/05/13 00:24
 
字数 2975
 
阅读 37
 
收藏 0
  

这里主要讲一下Provider注册方式,需要使用@Inject

正常我们注册一个服务像下面一样,假设一个logService服务

  providers: [logService],

实际上它的完整形式如下

  providers: [{provide: logService, useClass: logService}]

其中provide属性是令牌 (token),它作为键值 (key) 使用,用于定位依赖值和注册提供商。useClass则告诉provider用那个服务类去创建实例。

Provider注册方式

  • 类Provider ( ClassProvider )

  • 值Provider ( ValueProvider )

  • 别名Provider ( ExistingProvider )

  • 工厂Provider ( FactoryProvider 主要用于动态创建依赖)

类Provider

Provider就像上面的例子一样,用useClass服务类来创建实例,用provide声明标识。

值Provider

Provider指定依赖对象为常量、字符串、对象或其他数据类型的值。在创建Provider对象是使用useValue

  providers: [{provide: 'name', useValue: '王小二'}],

在使用时需要在构造函数中使用@Inject(),例如

constructor(@Inject('name') public n: string) {}

别名Provider

让多个标识同时指向一个实例

  providers: [
    {provide: NewService, useClass: NewService},
    {provide: NewService, useExisting: NewService},
  ],

工厂Provider

function serviceFactory() { ... }

const provider: FactoryProvider = {provide: 'someToken', useFactory: serviceFactory, deps: []};

useFactory : Function
A function to invoke to create a value for this token. The function is invoked with resolved values of tokens in the deps field.通过条件对输入的多个依赖对象参数进行判断,返回最终依赖的对象

deps : any[]
A list of tokens which need to be resolved by the injector. The list of values is then used as arguments to the useFactory function.用于工厂方法的依赖对象参数

multi : boolean
If true, then injector returns an array of instances. This is useful to allow multiple providers spread across many files to provide configuration information to a common token.

感觉不对劲啊...

以下为官网内容:

注入器的提供商们

提供商提供依赖值的一个具体的、运行时的版本。 注入器依靠提供商创建服务的实例,注入器再将服务的实例注入组件或其它服务。

必须为注入器注册一个服务的提供商,否则它不知道该如何创建该服务。

我们在前面通过AppModule元数据中的providers数组注册过Logger服务,就像这样:

Copy Code

providers: [Logger]

有很多方式可以提供一些实现 Logger类的东西。 Logger类本身是一个显而易见而且自然而然的提供商。 但它不是唯一的选项。

可以用其它备选提供商来配置注入器,只要它们能交付一个行为类似于Logger的对象就可以了。 可以提供一个替代类。你可以提供一个类似日志的对象。 可以给它一个提供商,让它调用可以创建日志服务的工厂函数。 所有这些方法,只要用在正确的场合,都可能是一个好的选择。

最重要的是,当注入器需要一个Logger时,它得先有一个提供商。

Provider类和一个提供商的字面量

像下面一样写providers数组:

Copy Code

providers: [Logger]

这其实是用于注册提供商的简写表达式。 使用的是一个带有两个属性的提供商对象字面量:

Copy Code

[{ provide: Logger, useClass: Logger }]

第一个是令牌 (token),它作为键值 (key) 使用,用于定位依赖值和注册提供商。

The first is the token that serves as the key for both locating a dependency value and registering the provider.

第二个是一个提供商定义对象。 可以把它看做是指导如何创建依赖值的配方。 有很多方式创建依赖值…… 也有很多方式可以写配方。

备选的类提供商

某些时候,我们会请求一个不同的类来提供服务。 下列代码告诉注入器,当有人请求Logger时,返回BetterLogger

Copy Code

[{ provide: Logger, useClass: BetterLogger }]

带依赖的类提供商

假设EvenBetterLogger可以在日志消息中显示用户名。 这个日志服务从注入的UserService中取得用户, UserService通常也会在应用级注入。

Copy Code

@Injectable()
class EvenBetterLogger extends Logger {
  constructor(private userService: UserService) { super(); }

  log(message: string) {
    let name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}

就像之前在BetterLogger中那样配置它。

Configure it like BetterLogger.

Copy Code

[ UserService,
  { provide: Logger, useClass: EvenBetterLogger }]

别名类提供商

假设某个旧组件依赖一个OldLogger类。 OldLoggerNewLogger具有相同的接口,但是由于某些原因, 我们不能升级这个旧组件并使用它。

组件想使用OldLogger记录消息时,我们希望改用NewLogger的单例对象来记录。

不管组件请求的是新的还是旧的日志服务,依赖注入器注入的都应该是同一个单例对象。 也就是说,OldLogger应该是NewLogger的别名。

我们当然不会希望应用中有两个不同的NewLogger实例。 不幸的是,如果尝试通过useClass来把OldLogger作为NewLogger的别名,就会导致这样的后果。

Copy Code

[ NewLogger,
  // Not aliased! Creates two instances of `NewLogger`
  { provide: OldLogger, useClass: NewLogger}]

解决方案:使用useExisting选项指定别名。

Copy Code

[ NewLogger,
  // Alias OldLogger w/ reference to NewLogger
  { provide: OldLogger, useExisting: NewLogger}]

值提供商

有时,提供一个预先做好的对象会比请求注入器从类中创建它更容易。

Copy Code

// An object in the shape of the logger service
let silentLogger = {
  logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
  log: () => {}
};

于是可以通过useValue选项来注册提供商,它会让这个对象直接扮演 logger 的角色。

Copy Code

[{ provide: Logger, useValue: silentLogger }]

查看更多useValue的例子,见非类依赖和 InjectionToken部分。

工厂提供商

有时,我们需要动态创建这个依赖值,因为它所需要的信息直到最后一刻才能确定。 也许这个信息会在浏览器的会话中不停地变化。

还假设这个可注入的服务没法通过独立的源访问此信息。

这种情况下,请调用工厂提供商

下面通过添加新的业务需求来说明这一点: HeroService 必须对普通用户隐藏掉秘密英雄。 只有授权用户才能看到秘密英雄。

就像EvenBetterLogger那样,HeroService需要了解此用户的身份。 它需要知道,这个用户是否有权看到隐藏英雄。 这个授权可能在单一的应用会话中被改变,例如,改用另一个用户的身份登录时。

EvenBetterLogger不同,不能把UserService注入到HeroService中。 HeroService无权访问用户信息,来决定谁有授权谁没有授权。

HeroService的构造函数带上一个布尔型的标志,来控制是否显示隐藏的英雄。

src/app/heroes/hero.service.ts (excerpt)

Copy Code

constructor(
  private logger: Logger,
  private isAuthorized: boolean) { }

getHeroes() {
  let auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
  this.logger.log(`Getting heroes for ${auth} user.`);
  return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}

我们可以注入Logger,但是不能注入逻辑型的isAuthorized。 我们不得不通过通过工厂提供商创建这个HeroService的新实例。

工厂提供商需要一个工厂方法:

src/app/heroes/hero.service.provider.ts (excerpt)

Copy Code

let heroServiceFactory = (logger: Logger, userService: UserService) => {
  return new HeroService(logger, userService.user.isAuthorized);
};

虽然HeroService不能访问UserService,但是工厂方法可以。

同时把LoggerUserService注入到工厂提供商中,并且让注入器把它们传给工厂方法:

src/app/heroes/hero.service.provider.ts (excerpt)

Copy Code

export let heroServiceProvider =
  { provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  };

useFactory字段告诉 Angular:这个提供商是一个工厂方法,它的实现是heroServiceFactory

deps属性是提供商令牌数组。 LoggerUserService类作为它们自身类提供商的令牌。 注入器解析这些令牌,把相应的服务注入到工厂函数中相应的参数中去。

注意,我们在一个导出的变量中捕获了这个工厂提供商:heroServiceProvider。 这个额外的步骤让工厂提供商可被复用。 无论哪里需要,都可以使用这个变量注册HeroService

在这个例子中,只在HeroesComponent中需要它, 这里,它代替了元数据providers数组中原来的HeroService注册。 对比一下新的和旧的实现:

src/app/heroes/heroes.component (v3)src/app/heroes/heroes.component (v2)

Copy Code

 
  1. import { Component } from '@angular/core';

  2.  

  3. import { heroServiceProvider } from './hero.service.provider';

  4.  

  5. @Component({

  6. selector: 'my-heroes',

  7. template: `

  8. <h2>Heroes</h2>

  9. <hero-list></hero-list>

  10. `,

  11. providers: [heroServiceProvider]

  12. })

  13. export class HeroesComponent { }

依赖注入令牌

当向注入器注册提供商时,实际上是把这个提供商和一个 DI 令牌关联起来了。 注入器维护一个内部的令牌-提供商映射表,这个映射表会在请求依赖时被引用到。 令牌就是这个映射表中的键值。

在前面的所有例子中,依赖值都是一个类实例,并且类的类型作为它自己的查找键值。 在下面的代码中,HeroService类型作为令牌,直接从注入器中获取HeroService 实例:

Copy Code

heroService: HeroService;

编写需要基于类的依赖注入的构造函数对我们来说是很幸运的。 只要定义一个HeroService类型的构造函数参数, Angular 就会知道把跟HeroService类令牌关联的服务注入进来:

Copy Code

constructor(heroService: HeroService)

这是一个特殊的规约,因为大多数依赖值都是以类的形式提供的。

非类依赖

如果依赖值不是一个类呢?有时候想要注入的东西是一个字符串,函数或者对象。

应用程序经常为很多很小的因素定义配置对象(例如应用程序的标题或网络API终点的地址)。 但是这些配置对象不总是类的实例,它们可能是对象,如下面这个:

src/app/app-config.ts (excerpt)

Copy Code

export interface AppConfig {
  apiEndpoint: string;
  title: string;
}

export const HERO_DI_CONFIG: AppConfig = {
  apiEndpoint: 'api.heroes.com',
  title: 'Dependency Injection'
};

我们想让这个配置对象在注入时可用,而且知道可以使用值提供商来注册一个对象。

但是,这种情况下用什么作令牌呢? 我们没办法找一个类来当作令牌,因为没有Config类。

TypeScript 接口不是一个有效的令牌

CONFIG常量有一个接口:AppConfig。不幸的是,不能把 TypeScript 接口用作令牌:

Copy Code

// FAIL! Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]

Copy Code

// FAIL! Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }

对于习惯于在强类型的语言中使用依赖注入的开发人员,这会看起来很奇怪, 因为在强类型语言中,接口是首选的用于查找依赖的主键。

这不是 Angular 的错。接口只是 TypeScript 设计时 (design-time) 的概念。JavaScript 没有接口。 TypeScript 接口不会出现在生成的 JavaScript 代码中。 在运行期,没有接口类型信息可供 Angular 查找。

InjectionToken

解决方案是为非类依赖定义和使用InjectionToken作为提供商令牌。 定义方式是这样的:

Copy Code

import { InjectionToken } from '@angular/core';

export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');

类型参数,虽然是可选的,但可以向开发者和开发工具传达类型信息。 而且这个令牌的描述信息也可以为开发者提供帮助。

使用这个InjectionToken对象注册依赖的提供商:

Copy Code

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

现在,在@Inject装饰器的帮助下,这个配置对象可以注入到任何需要它的构造函数中:

Copy Code

constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

虽然AppConfig接口在依赖注入过程中没有任何作用,但它为该类中的配置对象提供了强类型信息。

或者在 ngModule 中提供并注入这个配置对象,如AppModule

src/app/app.module.ts (ngmodule-providers)

Copy Code

providers: [
  UserService,
  { provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
],

可选依赖

HeroService需要一个Logger,但是如果想不提供 Logger 也能得到它,该怎么办呢? 可以把构造函数的参数标记为@Optional(),告诉 Angular 该依赖是可选的:

Copy Code

import { Optional } from '@angular/core';

Copy Code

constructor(@Optional() private logger: Logger) {
  if (this.logger) {
    this.logger.log(some_message);
  }
}

当使用@Optional()时,代码必须准备好如何处理空值。 如果其它的代码没有注册一个 logger,注入器会设置该logger的值为空 null。

来自  http://ju.outofmemory.cn/entry/291586

普通分类: