题 angular.service vs angular.factory


我见过两者 angular.factory() 和 angular.service() 用于申报服务;但是,我 找不到  angular.service 官方文档中的任何地方

这两种方法有什么区别?应该用什么(假设他们做不同的事情)?


1035
2018-01-14 18:36


起源


可能重复 对服务与工厂混淆 - Mark Rajcok
我搜索了“[angularjs]服务工厂”,但我也记得已经有一个关于它的问题(因为我曾想过在某一点上自己写这个/这个问题)。 - Mark Rajcok
在搜索中,方括号是否表示标记? - jacob
@Jacob Square Brackets正在缩小您的搜索范围。 [angularjs]指令 - 将针对已经使用angularjs标记的问题搜索“指令”。 - Mahbub
在“module.service”和“module.factory”旁边,还有两种在AngularJS中创建服务的方法。欲了解更多信息,请访问博文:“如何以4种不同的方式创建(单例)AngularJS服务“ - Emil van Galen


答案:


  angular.service('myService', myServiceFunction);
  angular.factory('myFactory', myFactoryFunction);

我无法绕过这个概念,直到我这样对待自己:

服务功能 你写的将是 -ed:

  myInjectedService  <----  new myServiceFunction()

功能 你写的(构造函数)将是 调用

  myInjectedFactory  <---  myFactoryFunction()

你用它做什么取决于你,但有一些有用的模式......

如写一个 服务 公开API的函数:

function myServiceFunction() {
  this.awesomeApi = function(optional) {
    // calculate some stuff
    return awesomeListOfValues;
  }
}
---------------------------------------------------------------------------------
// Injected in your controller
$scope.awesome = myInjectedService.awesomeApi();

或使用  公开API的函数:

function myFactoryFunction() {
  var aPrivateVariable = "yay";

  function hello() {
    return "hello mars " + aPrivateVariable;
  }

  // expose a public API
  return {
    hello: hello
  };
}
---------------------------------------------------------------------------------
// Injected in your controller
$scope.hello = myInjectedFactory.hello();

或使用  函数返回一个构造函数:

function myFactoryFunction() {
    return function() {
        var a = 2;
        this.a2 = function() {
            return a*2;
        };
    };
}
---------------------------------------------------------------------------------
// Injected in your controller
var myShinyNewObject = new myInjectedFactory();
$scope.four = myShinyNewObject.a2();

哪一个使用?...

你可以用两者完成同样的事情。但是,在某些情况下  为您提供更灵活的创建具有更简单语法的注入。这是因为myInjectedService必须始终是一个对象,myInjectedFactory可以是一个对象,一个函数引用或任何值。例如,如果您编写了一个服务来创建构造函数(如上面的上一个示例所示),则必须像这样实例化:

var myShinyNewObject = new myInjectedService.myFunction()

这可能比这更不可取:

var myShinyNewObject = new myInjectedFactory();

(但你应该首先考虑使用这种类型的模式,因为 控制器中的对象会创建难以模拟测试的难以跟踪的依赖项。最好让服务管理一组对象而不是使用 new() 老谋深算愿意不愿意。)


还有一件事,他们都是单身人士......

另外请记住,在这两种情况下,angular都可以帮助您管理单身人士。无论您注入服务或功能的位置或次数,您都将获得对同一对象或功能的相同引用。 (除了工厂只返回一个数字或字符串之类的值。在这种情况下,您将始终获得相同的值,但不是引用。)


1247
2018-02-20 06:46



将它称为对象构造函数比Newable更好吗? - marksyzm
@Hugo,我证明你可以用两者有效地完成同样的事情,只是语法会有所不同。 - Gil Birman
在我确信它们都是必要的之前,我不知道有多少次我必须阅读服务和工厂之间的区别 - DMac the Destroyer
我们已经有一个动词来说“新”,它是“实例化”。仅供参考。 :) - sscarduzio
工厂是被调用的函数,因此它们可以返回任何东西。另一方面,服务由角度过孔实例化 new fn(),所以他们必须返回一个实例。 - Gil Birman


简单的说 ..

// Service
service = (a, b) => {
  a.lastName = b;
  return a;
};

// Factory
factory = (a, b) => Object.assign({}, a, { lastName: b });

const fullName = { firstName: 'john' };

// Service
const lastNameService = (a, b) => {
  a.lastName = b;
  return a;
};
console.log(lastNameService(fullName, 'doe'));

// Factory
const lastNameFactory = (a, b) => 
  Object.assign({}, a, { lastName: b })
console.log(lastNameFactory(fullName, 'doe'));


316
2018-01-08 02:05



伙计,谢谢。并不是说其他​​答案的细节无效,但有时你需要10秒版本。 - R Claven
@pixelbits你能推荐一个编辑吗? - Kirk Strobeck
只是让服务功能什么都不返回。该 this.name = ... 足以证明它暴露了一个API。 - pixelbits
但是,如果你确实返回并反对它将使用它而不是这个。 jsfiddle.net/Ne5P8/1221 - MrB
@pixelbits我需要你进行编辑 - Kirk Strobeck


以下是主要差异:

服务

句法: module.service( 'serviceName', function );

结果:将serviceName声明为可注入参数时,将为您提供 函数的实例 传递给 module.service

用法:可能有用 共享实用功能 通过简单附加来调用很有用 ( ) 到注入的函数引用。也可以运行 injectedArg.call( this ) 或类似的。

工厂

句法: module.factory( 'factoryName', function );

结果:当将factoryName声明为可注入参数时,将为您提供 通过调用函数引用返回的值 传递给 module.factory

用法:可用于返回a '类' 然后可以用来创建实例的函数。

这是 使用服务和工厂的例子。了解更多 AngularJS服务与工厂

你也可以检查一下 AngularJS文档 和stackoverflow上的类似问题 对服务与工厂混淆


243
2018-04-09 23:18



我不同意你对工厂的使用示例。服务和工厂(假设函数被返回。它可能只是一个值或一个对象)可以是新的。实际上,服务是唯一可以保证是新的选项,因为您提供了一个函数实例。我想说使用FACTORY而不是服务的好处是,它允许对属性的访问进行一些控制 - 私有和公共本身,而服务的所有属性本质上都是暴露的。我认为供应商是工厂的工厂 - 只有在配置时可注射和配置。 - Drew R
@DrewR感谢您的评论,我找到了使用Factory的公共和私有方法的一个很好的例子: stackoverflow.com/a/14904891/65025 - edzillion
实际上,我必须同意@DrewR。我之前曾经使用工厂来归还物品,但老实说,在这一点上使用它可能是值得的 $providers 每时每刻。 - jedd.ahyoung
service是自动实例化构造函数,对吧? - Martian2049
@DrewR - 根据我的理解,你可以通过工厂实现与服务相同的新效果,但这不是它的意思。它的主要目标是当您只想返回一些实用程序对象时,它提供了更合适的语法 - 您可以简单地编写 this.myFunc = function(){}在您的服务中(免于编写代码来创建对象,就像您必须与工厂一样)。 - BornToCode


TL; DR 

1) 当你使用的时候  您创建一个对象,向其添加属性,然后返回该对象。当您将此工厂传递到控制器时,该对象上的这些属性现在将通过您的工厂在该控制器中可用。

app.controller('myFactoryCtrl', function($scope, myFactory){
  $scope.artist = myFactory.getArtist();
});

app.factory('myFactory', function(){
  var _artist = 'Shakira';
  var service = {};

  service.getArtist = function(){
    return _artist;
  }

  return service;
});


2) 当你使用时 服务,Angular使用'new'关键字在幕后实例化它。因此,您将向“this”添加属性,服务将返回“this”。当您将服务传递到控制器时,“this”上的这些属性现在将通过您的服务在该控制器上可用。

app.controller('myServiceCtrl', function($scope, myService){
  $scope.artist = myService.getArtist();
});

app.service('myService', function(){
  var _artist = 'Nelly';
  this.getArtist = function(){
    return _artist;
  }
});



非TL; DR

1)工厂 
工厂是最流行的创建和配置服务的方式。真的没有比TL更多的东西了; DR说。您只需创建一个对象,向其添加属性,然后返回该对象。然后,当您将工厂传递到控制器时,该对象上的这些属性现在将通过您的工厂在该控制器中可用。下面是一个更广泛的例子。

app.factory('myFactory', function(){
  var service = {};
  return service;
});

现在,当我们将'myFactory'传递给我们的控制器时,我们可以使用我们附加到'service'的任何属性。

现在让我们在回调函数中添加一些“私有”变量。这些不能直接从控制器访问,但我们最终会在'service'上设置一些getter / setter方法,以便在需要时能够改变这些'private'变量。

app.factory('myFactory', function($http, $q){
  var service = {};
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
   _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
    return _finalUrl
  }

  return service;
});

在这里你会注意到我们没有将这些变量/函数附加到'service'。我们只是创建它们以便以后使用或修改它们。

  • baseUrl是iTunes API所需的基本URL
  • _artist是我们想要查找的艺术家
  • _finalUrl是最终完全构建的URL,我们将调用iTunes makeUrl是一个函数,它将创建并返回我们的 iTunes友好的URL。

现在我们的助手/私有变量和函数已经到位,让我们为'service'对象添加一些属性。无论我们采用什么样的“服务”,我们都可以直接使用我们传递给“myFactory”的控制器。

我们将创建setArtist和getArtist方法,只返回或设置艺术家。我们还将创建一个方法,使用我们创建的URL调用iTunes API。一旦数据从iTunes API返回,此方法将返回一个承诺。如果您在Angular中没有使用Promises的经验,我强烈建议您深入了解它们。

下面 setArtist 接受艺术家并允许您设置艺术家。 getArtist 返回艺术家callItunes首先调用makeUrl()以构建我们将用$ http请求使用的URL。然后它设置一个promise对象,用我们的最终url发出$ http请求,然后因为$ http返回一个promise,我们可以在我们的请求之后调用.success或.error。然后我们使用iTunes数据解决我们的承诺,或者我们拒绝它并显示“有错误”的消息。

app.factory('myFactory', function($http, $q){
  var service = {};
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }

  service.setArtist = function(artist){
    _artist = artist;
  }

  service.getArtist = function(){
    return _artist;
  }

  service.callItunes = function(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

  return service;
});

现在我们的工厂已经完工。我们现在能够将'myFactory'注入任何控制器,然后我们就可以调用附加到服务对象(setArtist,getArtist和callItunes)的方法。

app.controller('myFactoryCtrl', function($scope, myFactory){
  $scope.data = {};
  $scope.updateArtist = function(){
    myFactory.setArtist($scope.data.artist);
  };

  $scope.submitArtist = function(){
    myFactory.callItunes()
      .then(function(data){
        $scope.data.artistData = data;
      }, function(data){
        alert(data);
      })
  }
});

在上面的控制器中,我们注入了'myFactory'服务。然后,我们在$ scope对象上设置属性来自'myFactory'的数据。上面唯一棘手的代码是你以前从未处理过承诺。因为callItunes正在返回一个promise,所以我们可以使用.then()方法,只有在我们的承诺与iTunes数据一起完成后才设置$ scope.data.artistData。你会发现我们的控制器很“瘦”。我们所有的逻辑和持久数据都位于我们的服务中,而不是我们的控制器中。

2)服务 
在处理创建服务时,最重要的事情可能是它使用'new'关键字进行实例化。对于JavaScript JavaScript专家来说,这应该会给你一个关于代码本质的一个很大的暗示。对于那些JavaScript背景有限的人或那些不太熟悉'new'关键字实际执行者的人,让我们回顾一下最终将帮助我们理解服务性质的一些JavaScript基础知识。

要真正看到使用'new'关键字调用函数时发生的更改,让我们创建一个函数并使用'new'关键字调用它,然后让我们看看解释器在看到'new'关键字时的作用。最终结果将是相同的。

首先让我们创建我的构造函数。

var Person = function(name, age){
  this.name = name;
  this.age = age;
}

这是一个典型的JavaScript构造函数。现在每当我们使用'new'关键字调用Person函数时,'this'将绑定到新创建的对象。

现在让我们在Person的原型上添加一个方法,以便它可以在Person'类'的每个实例上使用。

Person.prototype.sayName = function(){
  alert('My name is ' + this.name);
}

现在,因为我们将sayName函数放在原型上,所以Person的每个实例都能够调用sayName函数,以便提示实例的名称。

现在我们在其原型上有Person构造函数和sayName函数,让我们实际创建Person的实例然后调用sayName函数。

var tyler = new Person('Tyler', 23);
tyler.sayName(); //alerts 'My name is Tyler'

因此,创建Person构造函数的代码,向其原型添加函数,创建Person实例,然后在其原型上调用函数就像这样。

var Person = function(name, age){
  this.name = name;
  this.age = age;
}
Person.prototype.sayName = function(){
  alert('My name is ' + this.name);
}
var tyler = new Person('Tyler', 23);
tyler.sayName(); //alerts 'My name is Tyler'

现在让我们看一下在JavaScript中使用'new'关键字时实际发生的情况。你应该注意的第一件事是在我们的例子中使用'new'后,我们能够在'tyler'上调用一个方法(sayName),就像它是一个对象一样 - 那是因为它是。首先,我们知道我们的Person构造函数正在返回一个对象,我们是否可以在代码中看到它。其次,我们知道因为我们的sayName函数位于原型而不是直接位于Person实例上,所以Person函数返回的对象必须在失败的查找中委托给它的原型。换句话说,当我们调用tyler.sayName()时,解释器会说“好了,我将查看我们刚刚创建的'tyler'对象,找到sayName函数,然后调用它。等一下,我在这里看不到 - 我只看到名字和年龄,让我检查原型。是的,看起来像是在原型上,让我称之为。“

下面是您如何思考'new'关键字在JavaScript中实际执行的操作的代码。它基本上是上一段的代码示例。我把'解释器视图'或解释器看到注释中的代码的方式。

var Person = function(name, age){
  //The line below this creates an obj object that will delegate to the person's prototype on failed lookups.
  //var obj = Object.create(Person.prototype);

  //The line directly below this sets 'this' to the newly created object
  //this = obj;

  this.name = name;
  this.age = age;

  //return this;
}

现在了解“新”关键字在JavaScript中的实际功能,在Angular中创建服务应该更容易理解。

创建服务时要了解的最重要的事情是知道服务是使用'new'关键字实例化的。将这些知识与上面的示例相结合,您现在应该认识到您将把属性和方法直接附加到'this',然后从服务本身返回。我们来看看这个实际情况。

与我们最初对Factory示例所做的不同,我们不需要创建对象然后返回该对象,因为像之前多次提到的那样,我们使用'new'关键字,因此解释器将创建该对象,让它委托给它是原型,然后在没有我们完成工作的情况下将它归还给我们。

首先,让我们创建我们的'私人'和帮助函数。这应该看起来非常熟悉,因为我们对我们的工厂做了完全相同的事情。我不会解释每一行在这里的作用,因为我在工厂示例中这样做,如果您感到困惑,请重新阅读工厂示例。

app.service('myService', function($http, $q){
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }
});

现在,我们将把我们控制器中可用的所有方法附加到'this'。

app.service('myService', function($http, $q){
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }

  this.setArtist = function(artist){
    _artist = artist;
  }

  this.getArtist = function(){
    return _artist;
  }

  this.callItunes = function(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

});

现在就像我们的工厂一样,setArtist,getArtist和callItunes将在我们传递myService的控制器中可用。这是myService控制器(几乎与我们的工厂控制器完全相同)。

app.controller('myServiceCtrl', function($scope, myService){
  $scope.data = {};
  $scope.updateArtist = function(){
    myService.setArtist($scope.data.artist);
  };

  $scope.submitArtist = function(){
    myService.callItunes()
      .then(function(data){
        $scope.data.artistData = data;
      }, function(data){
        alert(data);
      })
  }
});

就像我之前提到的,一旦你真正理解了什么是'新',服务几乎与Angular的工厂相同。


134
2018-05-14 16:14



您可能希望直接提供指向您博客的链接。 tylermcginnis.com/angularjs-factory-vs-service-vs-provider 我发现它更容易阅读。 - Tyler Collier
Nothin在这里重复你的博客是错误的,但我同意这是一篇greta博客文章。 - R Claven
详细解释每个人在幕后做了什么,但仍然不清楚为什么以及何时有人选择在工厂使用服务。换句话说,我什么时候会更喜欢新建对象而不是工厂返回的对象。我认为这是最大的混乱。 - demisx
基本上,如果要创建与远程服务的持久连接,例如示例中提到的具有常量连接(连接状态,呼叫历史记录,数据存储)的iTunes API,则可以使用Factory。如果您将其作为服务实现,那么每次您需要API中的某些内容时,您将重新创建连接,并且无法在其中存储任何内容。因为每次重新创建服务时,都会得到一个空白/默认对象。 - Meki
我不认为这是正确的,@ Aznim。像其他人所说,两者都提供单身人士。 - Cryptovirus


线索就在名字中

服务和工厂彼此相似。两者都将产生可以注入其他对象的单个对象,因此通常可以互换使用。

它们旨在用于语义上以实现不同的设计模式。

服务用于实现服务模式

服务模式是指将应用程序分解为逻辑上一致的功能单元的模式。一个示例可能是API访问器或一组业务逻辑。

这在Angular中尤其重要,因为Angular模型通常只是从服务器中提取的JSON对象,因此我们需要在某处放置我们的业务逻辑。

这是一个Github服务。它知道如何与Github交谈。它知道网址和方法。我们可以将它注入控制器,它将生成并返回一个promise。

(function() {
  var base = "https://api.github.com";

  angular.module('github', [])
    .service('githubService', function( $http ) {
      this.getEvents: function() {
        var url = [
          base,
          '/events',
          '?callback=JSON_CALLBACK'
        ].join('');
        return $http.jsonp(url);
      }
    });
  )();

工厂实施工厂模式

另一方面,工厂旨在实施工厂模式。我们使用工厂函数生成对象的工厂模式。通常我们可能会将其用于构建模型。这是一个返回Author构造函数的工厂:

angular.module('user', [])
  .factory('User', function($resource) {
    var url = 'http://simple-api.herokuapp.com/api/v1/authors/:id'
    return $resource(url);
  })

我们会像这样使用它:

angular.module('app', ['user'])
  .controller('authorController', function($scope, User) {
    $scope.user = new User();
  })

请注意,工厂也会返回单身人士。

工厂可以返回构造函数

因为工厂只返回一个对象,所以它可以返回你喜欢的任何类型的对象,包括构造函数,如上所示。

工厂返回一个对象;服务是新的

另一个技术差异在于服务和工厂的组成方式。将新建服务功能以生成对象。将调用工厂函数并返回该对象。

  • 服务是新的构造函数。
  • 只需调用工厂并返回一个对象。

这意味着在服务中,我们附加到“this”,它在构造函数的上下文中将指向正在构造的对象。

为了说明这一点,这里是使用服务和工厂创建的同一个简单对象:

angular.module('app', [])
  .service('helloService', function() {
    this.sayHello = function() {
      return "Hello!";
    }
  })
  .factory('helloFactory', function() {
    return {
      sayHello: function() {
        return "Hello!";
      }
    }
  });

33
2017-12-18 11:48



很好的解释,谢谢!还有一种类型 工厂 示例代码在哪里 Author 喷油器参数应该是 Person。 - mik-t
谢谢@ mik-T,我修正了错别字。 - superluminary
您对服务模式的使用不正确 - 这应该是工厂。如果你调用.factory()而不是.service(),你会发现它的工作方式完全相同。服务模式旨在提供构造函数,而不是返回新对象的函数。 Angular(有效地)在构造函数上调用“new”。服务工作的唯一原因是,如果在返回对象的构造函数上调用“new”,实际上会返回返回的对象而不是构造的对象。工厂可以用来创造你想要的任何东西,而不仅仅是模型。 - Dan King
@DanKing - 你当然是对的。更新答案...... - superluminary


app.factory('fn',fn)与app.service('fn',fn)

施工

对于工厂,Angular将调用该函数来获得结果。它是缓存和注入的结果。

 //factory
 var obj = fn();
 return obj;

使用服务,Angular将通过调用来调用构造函数 。构造的函数被缓存并注入。

  //service
  var obj = new fn();
  return obj;

履行

工厂通常返回一个对象字面值,因为返回值  注入控制器,运行块,指令等的内容

  app.factory('fn', function(){
         var foo = 0;
         var bar = 0;
         function setFoo(val) {
               foo = val;
         }
         function setBar (val){
               bar = val;
         }
         return {
                setFoo: setFoo,
                serBar: setBar
         }
  });

服务功能通常不返回任何内容。相反,它们执行初始化和公开函数。函数也可以引用'this',因为它是使用'new'构造的。

app.service('fn', function () {
         var foo = 0;
         var bar = 0;
         this.setFoo = function (val) {
               foo = val;
         }
         this.setBar = function (val){
               bar = val;
         }
});

结论

在使用工厂或服务时,它们都非常相似。它们被注入到控制器,指令,运行块等中,并以几乎相同的方式在客户端代码中使用。它们也都是单例 - 意味着在注入服务/工厂的所有地方之间共享相同的实例。

那你应该选哪个?任何一个 - 他们是如此相似,以至于差异是微不足道的。如果您确实选择了另一个,请注意它们的构造方式,以便您可以正确实现它们。


23
2018-06-27 00:17



服务函数不“不返回任何东西”,它们隐式返回构造的对象,如果你没有指定自己的return语句(在后一种情况下,你返回的对象是将创建和缓存的对象,类似于工厂)。 - Cryptovirus
我认为你误解了它...当我说回归时,我的意思是从服务功能实现的角度来看 - pixelbits
你确定工厂也是单身镇吗? - Martian2049
是的,绝对正面:) - pixelbits


这里的所有答案似乎都围绕着服务和工厂,这是有效的,因为那是被问到的。但同样重要的是要记住,还有其他几个包括 provider()value(),和 constant()

要记住的关键是每一个都是另一个的特例。链中的每个特殊情况允许您使用更少的代码执行相同的操作。每个人也有一些额外的限制。

要决定何时使用,您只需看到哪一个允许您在更少的代码中执行您想要的操作。这是一张图片,说明它们有多相似:

enter image description here

有关完整的逐步细分和快速参考何时使用,您可以访问我从以下网站获取此图像的博文:

http://www.simplygoodcode.com/2015/11/the-difference-between-service-provider-and-factory-in-angularjs/


23
2017-12-10 12:55



@jacob可能是这样,但我认为不仅在何时使用每个的整体概念,而且它们都是同一事物的本质变化是一个重要的概念。 - Luis Perez
@LuisPerez您的博客链接和解释其差异的视频非常棒。通过视频中的这些示例更容易理解:) - Alin Ciocan