AngularJS:服务vs提供者vs工厂

3418
在AngularJS中,ServiceProviderFactory有什么区别?

249
我发现对于初学者而言,所有Angular术语都有些令人望而生畏。我们开始使用这份速成表格,使我们的程序员在学习Angular时更易于理解:http://demisx.github.io/angularjs/2014/09/14/angular-what-goes-where.html。希望这能对你的团队有所帮助。 - demisx
7
在我看来,理解这两者之间的区别最好的方法是使用Angular的官方文档:https://docs.angularjs.org/guide/providers ,该文档非常详细地解释了这个问题,并使用一个独特的例子帮助你理解。 - Rafael Merlin
3
感谢您,@Blaise!根据我在帖子中的评论,我故意省略了它,因为从我的经验来看,99%的用例都可以通过 service.factory 成功处理。我不想进一步复杂化这个主题。 - demisx
3
我认为这个讨论也非常有用 https://dev59.com/22Mk5IYBdhLWcg3w0RGU - Anand Gupta
3
这里有一些关于如何使用servicesfactoriesproviders的好答案。 - Mistalis
1
我认为这个话题的混淆是因为AngularJS在这个地方过度设计并且文档不够好。正如@demisx所提到的,似乎没有令人信服的用例需要使用服务而不是工厂,它们都是在提供程序之上的语法糖(与其他提供程序不同,可以在单例实例化之前在'module.config'中进行配置)。 - Mihail Kostira
30个回答

12
参考这个页面和文档(自上次我查看以来似乎有了很大的改进),我制作了以下真实(-ish)世界演示,它使用了4种5种提供程序的味道; 值,常数,工厂和完整的提供程序。
HTML:
<div ng-controller="mainCtrl as main">
    <h1>{{main.title}}*</h1>
    <h2>{{main.strapline}}</h2>
    <p>Earn {{main.earn}} per click</p>
    <p>You've earned {{main.earned}} by clicking!</p>
    <button ng-click="main.handleClick()">Click me to earn</button>
    <small>* Not actual money</small>
</div>

应用程序

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

// A CONSTANT is not going to change
app.constant('range', 100);

// A VALUE could change, but probably / typically doesn't
app.value('title', 'Earn money by clicking');
app.value('strapline', 'Adventures in ng Providers');

// A simple FACTORY allows us to compute a value @ runtime.
// Furthermore, it can have other dependencies injected into it such
// as our range constant.
app.factory('random', function randomFactory(range) {
    // Get a random number within the range defined in our CONSTANT
    return Math.random() * range;
});

// A PROVIDER, must return a custom type which implements the functionality 
// provided by our service (see what I did there?).
// Here we define the constructor for the custom type the PROVIDER below will 
// instantiate and return.
var Money = function(locale) {

    // Depending on locale string set during config phase, we'll
    // use different symbols and positioning for any values we 
    // need to display as currency
    this.settings = {
        uk: {
            front: true,
            currency: '£',
            thousand: ',',
            decimal: '.'
        },
        eu: {
            front: false,
            currency: '€',
            thousand: '.',
            decimal: ','
        }
    };

    this.locale = locale;
};

// Return a monetary value with currency symbol and placement, and decimal 
// and thousand delimiters according to the locale set in the config phase.
Money.prototype.convertValue = function(value) {

    var settings = this.settings[this.locale],
        decimalIndex, converted;

    converted = this.addThousandSeparator(value.toFixed(2), settings.thousand);

    decimalIndex = converted.length - 3;

    converted = converted.substr(0, decimalIndex) +
        settings.decimal +
        converted.substr(decimalIndex + 1);    

    converted = settings.front ?
            settings.currency + converted : 
            converted + settings.currency; 

    return converted;   
};

// Add supplied thousand separator to supplied value
Money.prototype.addThousandSeparator = function(value, symbol) {
   return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, symbol);
};

// PROVIDER is the core recipe type - VALUE, CONSTANT, SERVICE & FACTORY
// are all effectively syntactic sugar built on top of the PROVIDER construct
// One of the advantages of the PROVIDER is that we can configure it before the
// application starts (see config below).
app.provider('money', function MoneyProvider() {

    var locale;

    // Function called by the config to set up the provider
    this.setLocale = function(value) {
        locale = value;   
    };

    // All providers need to implement a $get method which returns
    // an instance of the custom class which constitutes the service
    this.$get = function moneyFactory() {
        return new Money(locale);
    };
});

// We can configure a PROVIDER on application initialisation.
app.config(['moneyProvider', function(moneyProvider) {
    moneyProvider.setLocale('uk');
    //moneyProvider.setLocale('eu'); 
}]);

// The ubiquitous controller
app.controller('mainCtrl', function($scope, title, strapline, random, money) {

    // Plain old VALUE(s)
    this.title = title;
    this.strapline = strapline;

    this.count = 0;

    // Compute values using our money provider    
    this.earn = money.convertValue(random); // random is computed @ runtime
    this.earned = money.convertValue(0);

    this.handleClick = function() { 
        this.count ++;
        this.earned = money.convertValue(random * this.count);
    };
});

工作中demo


11

这篇答案回答了如何将工厂、服务和常量视为提供程序配方的语法糖的问题。

或者

工厂、服务和提供程序在内部的相似之处是什么。

基本上发生的事情是,当你创建一个factory()时,它将第二个参数中提供的function设置为提供程序的$get并返回它(provider(name, {$get:factoryFn})),所有你得到的只是provider,但该provider除了$get方法/属性之外没有其他属性/方法(也就是说,你无法配置它)

factory源代码

function factory(name, factoryFn, enforce) {
    return provider(name, {
      $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
    });
};

当创建一个service()时,它将通过提供一个带有functionfactory()来注入constructor(返回您在服务中提供的构造函数的实例)并返回它。

服务的源代码

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

基本上,在这两种情况下,您最终都会将提供程序的 $get 设置为您提供的函数,但是您可以在 provider() 的配置块中最初提供任何额外的东西。


10

我知道很多出色的答案,但我必须分享一下我使用的经验:
1. 对于大多数情况下的默认选项,我使用service
2. 使用factory创建特定实例的服务

// factory.js ////////////////////////////
(function() {
'use strict';
angular
    .module('myApp.services')
    .factory('xFactory', xFactoryImp);
xFactoryImp.$inject = ['$http'];

function xFactoryImp($http) {
    var fac = function (params) {
        this._params = params; // used for query params
    };

    fac.prototype.nextPage = function () {
        var url = "/_prc";

        $http.get(url, {params: this._params}).success(function(data){ ...
    }
    return fac;
}
})();

// service.js //////////////////////////
(function() {
'use strict';
angular
    .module('myApp.services')
    .service('xService', xServiceImp);
xServiceImp.$inject = ['$http'];

function xServiceImp($http) {  
    this._params = {'model': 'account','mode': 'list'};

    this.nextPage = function () {
        var url = "/_prc";

        $http.get(url, {params: this._params}).success(function(data){ ...
    }       
}
})();

并使用:

controller: ['xFactory', 'xService', function(xFactory, xService){

        // books = new instance of xFactory for query 'book' model
        var books = new xFactory({'model': 'book', 'mode': 'list'});

        // accounts = new instance of xFactory for query 'accounts' model
        var accounts = new xFactory({'model': 'account', 'mode': 'list'});

        // accounts2 = accounts variable
        var accounts2 = xService;
... 

9

只是为了澄清事情,从AngularJS源代码中,您可以看到一个服务只是调用工厂函数,而工厂函数反过来又调用提供程序函数:

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

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

9

虽然我来晚了,但我认为这对那些想学习(或明确)使用工厂、服务和提供程序方法开发AngularJS自定义服务的人更有帮助。

我找到了这个视频,清楚地解释了使用工厂、服务和提供程序方法开发AngularJS自定义服务:

https://www.youtube.com/watch?v=oUXku28ex-M

源代码:http://www.techcbt.com/Post/353/Angular-JS-basics/how-to-develop-angularjs-custom-service

此处发布的代码直接从上述来源复制而来,以使读者受益。

基于“工厂”模式的自定义服务代码如下(包括同步和异步版本以及调用http服务):

var app = angular.module("app", []);
app.controller('emp', ['$scope', 'calcFactory',
  function($scope, calcFactory) {
    $scope.a = 10;
    $scope.b = 20;

    $scope.doSum = function() {
      //$scope.sum = calcFactory.getSum($scope.a, $scope.b); //synchronous
      calcFactory.getSum($scope.a, $scope.b, function(r) { //aynchronous
        $scope.sum = r;
      });
    };

  }
]);

app.factory('calcFactory', ['$http', '$log',
  function($http, $log) {
    $log.log("instantiating calcFactory..");
    var oCalcService = {};

    //oCalcService.getSum = function(a,b){
    // return parseInt(a) + parseInt(b);
    //};

    //oCalcService.getSum = function(a, b, cb){
    // var s = parseInt(a) + parseInt(b);
    // cb(s);
    //};

    oCalcService.getSum = function(a, b, cb) { //using http service

      $http({
        url: 'http://localhost:4467/Sum?a=' + a + '&b=' + b,
        method: 'GET'
      }).then(function(resp) {
        $log.log(resp.data);
        cb(resp.data);
      }, function(resp) {
        $log.error("ERROR occurred");
      });
    };

    return oCalcService;
  }
]);

"服务"方法的代码,用于自定义服务(这与“工厂”非常相似,但从语法角度来看不同):

var app = angular.module("app", []);
app.controller('emp', ['$scope', 'calcService', function($scope, calcService){
 $scope.a = 10;
 $scope.b = 20;

 $scope.doSum = function(){
  //$scope.sum = calcService.getSum($scope.a, $scope.b);
  
  calcService.getSum($scope.a, $scope.b, function(r){
   $scope.sum = r;
  });  
 };

}]);

app.service('calcService', ['$http', '$log', function($http, $log){
 $log.log("instantiating calcService..");
 
 //this.getSum = function(a,b){
 // return parseInt(a) + parseInt(b);
 //};

 //this.getSum = function(a, b, cb){
 // var s = parseInt(a) + parseInt(b);
 // cb(s);
 //};

 this.getSum = function(a, b, cb){
  $http({
   url: 'http://localhost:4467/Sum?a=' + a + '&b=' + b,
   method: 'GET'
  }).then(function(resp){
   $log.log(resp.data);
   cb(resp.data);
  },function(resp){
   $log.error("ERROR occurred");
  });
 };

}]);

"provider" 方法适用于定制服务的代码(如果您想开发可配置的服务,则这是必需的):

var app = angular.module("app", []);
app.controller('emp', ['$scope', 'calcService', function($scope, calcService){
 $scope.a = 10;
 $scope.b = 20;

 $scope.doSum = function(){
  //$scope.sum = calcService.getSum($scope.a, $scope.b);
  
  calcService.getSum($scope.a, $scope.b, function(r){
   $scope.sum = r;
  });  
 };

}]);

app.provider('calcService', function(){

 var baseUrl = '';

 this.config = function(url){
  baseUrl = url;
 };

 this.$get = ['$log', '$http', function($log, $http){
  $log.log("instantiating calcService...")
  var oCalcService = {};

  //oCalcService.getSum = function(a,b){
  // return parseInt(a) + parseInt(b);
  //};

  //oCalcService.getSum = function(a, b, cb){
  // var s = parseInt(a) + parseInt(b);
  // cb(s); 
  //};

  oCalcService.getSum = function(a, b, cb){

   $http({
    url: baseUrl + '/Sum?a=' + a + '&b=' + b,
    method: 'GET'
   }).then(function(resp){
    $log.log(resp.data);
    cb(resp.data);
   },function(resp){
    $log.error("ERROR occurred");
   });
  };  

  return oCalcService;
 }];

});

app.config(['calcServiceProvider', function(calcServiceProvider){
 calcServiceProvider.config("http://localhost:4467");
}]);

最后,该UI可适用于上述任何服务:

<html>
<head>
 <title></title>
 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js" ></script>
 <script type="text/javascript" src="t03.js"></script>
</head>
<body ng-app="app">
 <div ng-controller="emp">
  <div>
   Value of a is {{a}},
   but you can change
   <input type=text ng-model="a" /> <br>

   Value of b is {{b}},
   but you can change
   <input type=text ng-model="b" /> <br>

  </div>
  Sum = {{sum}}<br>
  <button ng-click="doSum()">Calculate</button>
 </div>
</body>
</html>


9

让我们简单地讨论 AngularJS 中处理业务逻辑的三种方式:(受 Yaakov 的 Coursera AngularJS 课程启发)

SERVICE:

语法:

app.js

 var app = angular.module('ServiceExample',[]);
 var serviceExampleController =
              app.controller('ServiceExampleController', ServiceExampleController);
 var serviceExample = app.service('NameOfTheService', NameOfTheService);

 ServiceExampleController.$inject = ['NameOfTheService'] //protects from minification of js files

function ServiceExampleController(NameOfTheService){
     serviceExampleController = this;
     serviceExampleController.data = NameOfTheService.getSomeData();
 }

function NameOfTheService(){
     nameOfTheService = this;
     nameOfTheService.data = "Some Data";
     nameOfTheService.getSomeData = function(){
           return nameOfTheService.data;
     }     
}

index.html

<div ng-controller = "ServiceExampleController as serviceExample">
   {{serviceExample.data}}
</div>

服务的特点:

  1. 惰性实例化:如果没有注入,它将永远不会被实例化。因此,要使用它,必须将其注入到模块中。
  2. 单例:如果注入到多个模块中,则所有模块只有对一个特定实例的访问权限。这是在不同控制器之间共享数据非常方便的原因。

工厂模式

首先让我们看一下语法:

app.js

var app = angular.module('FactoryExample',[]);
var factoryController = app.controller('FactoryController', FactoryController);
var factoryExampleOne = app.factory('NameOfTheFactoryOne', NameOfTheFactoryOne);
var factoryExampleTwo = app.factory('NameOfTheFactoryTwo', NameOfTheFactoryTwo);

//first implementation where it returns a function
function NameOfTheFactoryOne(){
   var factory = function(){
      return new SomeService();
    }
   return factory;
}

//second implementation where an object literal would be returned
function NameOfTheFactoryTwo(){
   var factory = {
      getSomeService : function(){
          return new SomeService();
       }
    };
   return factory;
}

现在在控制器中使用上述两个内容:

 var factoryOne = NameOfTheFactoryOne() //since it returns a function
 factoryOne.someMethod();

 var factoryTwo = NameOfTheFactoryTwo.getSomeService(); //accessing the object
 factoryTwo.someMethod();

工厂的特点:

  1. 遵循工厂设计模式。工厂是一个生产新对象或功能的中心地点。
  2. 不仅可以生产单例,还可以定制化服务。
  3. .service() 方法是一个工厂,总是产生同一类型的服务,即单例,并且没有任何简单的方式来配置它的行为。该 .service() 方法通常被用作不需要任何配置的快捷方式。

提供者

让我们先看一下语法:

angular.module('ProviderModule', [])
.controller('ProviderModuleController', ProviderModuleController)
.provider('ServiceProvider', ServiceProvider)
.config(Config); //optional

Config.$inject = ['ServiceProvider'];
function Config(ServiceProvider) {
  ServiceProvider.defaults.maxItems = 10; //some default value
}


ProviderModuleController.$inject = ['ServiceProvider'];
function ProviderModuleController(ServiceProvider) {
  //some methods
}

function ServiceProvider() {
  var provider = this;

  provider.defaults = {
    maxItems: 10
  };

  provider.$get = function () {
    var someList = new someListService(provider.defaults.maxItems);

    return someList;
  };
}

}

Provider的特点:

  1. Provider是在Angular中创建服务最灵活的方法。
  2. 我们不仅可以创建一个动态可配置的工厂,而且在使用该工厂时,通过provider方法,我们可以在整个应用程序的引导阶段自定义配置工厂一次。
  3. 然后可以在整个应用程序中使用具有自定义设置的工厂。换句话说,在应用程序启动之前,我们可以配置此工厂。实际上,在angular文档中提到,当我们使用.service或.factory方法配置我们的服务时,实际执行的是provider方法。
  4. $get是直接附加到provider实例的函数。该函数是一个工厂函数。换句话说,它就像我们用于向.factory方法提供服务的那个函数。在该函数中,我们创建自己的服务。这个函数,即$ get属性,是使提供商成为提供商的关键。AngularJS希望提供者有一个$value是Angular将视为工厂函数的函数属性。但使整个提供程序设置非常特殊的是,我们可以在服务提供程序内提供某些config对象,通常带有默认值,稍后可以在其中一个步骤中覆盖整个应用程序的配置。

7
基本上,Provider、Factory 和 Service 都是服务。当你只需要一个 $get() 函数时,Factory 是 Service 的一种特殊情况,它允许你用更少的代码编写。
Services、Factories 和 Providers 之间的主要区别在于它们的复杂性。Services 是最简单的形式,Factories 稍微复杂一些,而 Providers 可以在运行时进行配置。
以下是每种情况下使用的总结:
Factory:您提供的值需要基于其他数据进行计算。
Service:您返回带有方法的对象。
Provider:您想要在创建对象之前,在配置阶段对其进行配置。在应用程序完全初始化之前,在应用程序配置中主要使用 Provider。

ERM、Value、Factory、Service 和 Constant 只是提供程序配方的语法糖。Angularjs 文档 - providers - Sudarshan_SMD
是的,我同意,现在使用Angular 4,我们不再有这个问题了。 - eGhoul

6

工厂: 工厂是创建对象并返回它的实际位置。
服务: 服务只是一个使用this关键字定义函数的标准函数。
提供者: 提供者有一个 $get,您可以定义并将其用于获取返回数据的对象。


4

1.服务是单例对象,它们在必要时创建,并且直到应用程序生命周期结束(当浏览器关闭时)才被清除。控制器在不再需要时被销毁和清理。

2.使用factory()方法是创建服务的最简单方法。 factory()方法允许我们通过返回包含服务函数和服务数据的对象来定义服务。服务定义函数是我们放置可注入服务(如$http和$q)的地方。 例如:

angular.module('myApp.services')
.factory('User', function($http) { // injectables go here
var backendUrl = "http://localhost:3000"; var service = {
    // our factory definition
user: {},
setName: function(newName) {
      service.user['name'] = newName;
    },
setEmail: function(newEmail) { service.user['email'] = newEmail;
},
save: function() {
return $http.post(backendUrl + '/users', { user: service.user
}); }
};
return service; });

在我们的应用程序中使用factory()函数

由于我们可以在运行时简单地将它注入到需要的位置,因此在我们的应用程序中使用factory非常容易。

angular.module('myApp')
.controller('MainController', function($scope, User) {
  $scope.saveUser = User.save;
});
  1. 与factory()方法不同,service()方法允许我们通过定义构造函数来创建服务。我们可以使用原型对象来定义我们的服务,而不是一个原始的javascript对象。 类似于factory()方法,我们也将在函数定义中设置可注入项。
  2. 创建服务最基本的方法是使用provide()方法。这是唯一一种使用.config()函数配置服务的方法。 与之前的两种方法不同,我们将在一个明确定义的this.$get()函数定义中设置可注入项。

-3

语法糖就在于其中的差异。只需要提供者。换句话说,只有提供者才是真正的Angular,其他所有的都是派生出来的(为了减少代码)。还有一个简单版本,叫做Value(),它只返回值,没有计算或函数。即使Value也是从提供者派生出来的!

那么为什么会有这样的复杂性,为什么我们不能只使用提供者并忘记其他一切呢?它应该帮助我们更轻松地编写代码和更好地沟通。而玩笑式的回答是,它变得越复杂,框架的销售就会越好。


  • 一个可以返回值=Value的提供者
  • 一个可以实例化并返回=工厂(+ Value)的提供者
  • 一个可以实例化+执行操作=服务(+ 工厂,+ Value)的提供者
  • 一个提供者=必须包含名为 $get 的属性(+工厂,+服务,+值)

Angular注入提供了我们达成这个结论的第一个提示。

"$injector用于检索由提供者定义的对象实例"而不是服务或工厂,而是提供者。

更好的答案是: “Angular服务是由服务工厂创建的。这些服务工厂是函数,又由服务提供者创建。服务提供者是构造函数。当被实例化时,它们必须包含一个名为 $get 的属性,其中保存了服务工厂函数。”

所以精通提供者和注入器,一切都会落在适当的位置:)。而在TypeScript中,当通过继承IServiceProvider来实现$get时,情况变得更有趣。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接