题 AngularJS:服务与提供商vs工厂


a之间有什么区别? ServiceProvider 和 Factory 在AngularJS?


3167
2018-03-27 17:59


起源


我发现所有Angular术语对于初学者来说都是令人生畏的。我们从这个备忘单开始,这对于我们的程序员在学习Angular时更容易理解 demisx.github.io/angularjs/2014/09/14/...。希望这也有助于您的团队。 - demisx
在我看来,理解差异的最好方法是使用Angular自己的文档: docs.angularjs.org/guide/providers 这是非常好的解释,并使用一个特殊的例子来帮助您理解它。 - Rafael Merlin
@Blaise谢谢!根据我在帖子中的评论,我故意将其删除,因为根据我的经验,99%的用例可以通过以下方式成功处理 service.factory。不想进一步使这个主题复杂化。 - demisx
我发现这个讨论也非常有用 stackoverflow.com/questions/18939709/... - Anand Gupta
这里有一些很好的答案 如何 services, factories 和 providers 作品。 - Mistalis


答案:


从我获得的AngularJS邮件列表中 一个惊人的线程 这解释了服务与工厂与提供商及其注入使用情况。编译答案:

服务

句法: module.service( 'serviceName', function ); 
结果:将serviceName声明为可注入参数时 您将获得该功能的实例。换一种说法  new FunctionYouPassedToService()

工厂

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

供应商

句法: module.provider( 'providerName', function ); 
结果:将providerName声明为可注入参数时 你将获得  (new ProviderFunction()).$get()。在调用$ get方法之前实例化构造函数 - ProviderFunction 是传递给module.provider的函数引用。

提供商的优势在于可以在模块配置阶段配置它们。

看到 这里 提供的代码。

这是Misko的一个很好的进一步解释:

provide.value('a', 123);

function Controller(a) {
  expect(a).toEqual(123);
}

在这种情况下,喷射器只是按原样返回值。但是如果你想计算价值怎么办?然后使用工厂

provide.factory('b', function(a) {
  return a*2;
});

function Controller(b) {
  expect(b).toEqual(246);
}

所以 factory 是一个负责创建价值的函数。请注意,工厂函数可以请求其他依赖项。

但是如果你想成为更多的OO并拥有一个名为Greeter的课程怎么办?

function Greeter(a) {
  this.greet = function() {
    return 'Hello ' + a;
  }
}

然后要实例化你必须写

provide.factory('greeter', function(a) {
  return new Greeter(a);
});

然后我们可以在这样的控制器中要求'greeter'

function Controller(greeter) {
  expect(greeter instanceof Greeter).toBe(true);
  expect(greeter.greet()).toEqual('Hello 123');
}

但这太浪漫了。写这个的更简单的方法是 provider.service('greeter', Greeter);

但是如果我们想要配置它会怎么样呢 Greeter 注射前的课程?然后我们可以写

provide.provider('greeter2', function() {
  var salutation = 'Hello';
  this.setSalutation = function(s) {
    salutation = s;
  }

  function Greeter(a) {
    this.greet = function() {
      return salutation + ' ' + a;
    }
  }

  this.$get = function(a) {
    return new Greeter(a);
  };
});

然后我们可以这样做:

angular.module('abc', []).config(function(greeter2Provider) {
  greeter2Provider.setSalutation('Halo');
});

function Controller(greeter2) {
  expect(greeter2.greet()).toEqual('Halo 123');
}

作为旁注, servicefactory,和 value 都来自提供者。

provider.service = function(name, Class) {
  provider.provide(name, function() {
    this.$get = function($injector) {
      return $injector.instantiate(Class);
    };
  });
}

provider.factory = function(name, factory) {
  provider.provide(name, function() {
    this.$get = function($injector) {
      return $injector.invoke(factory);
    };
  });
}

provider.value = function(name, value) {
  provider.factory(name, function() {
    return value;
  });
};

2803
2017-07-30 10:20



也可以看看 stackoverflow.com/a/13763886/215945 讨论了服务和工厂之间的差异。 - Mark Rajcok
在编辑611中,我添加了角度常量和值的使用。为了证明已经显示的另一个的差异。 jsbin.com/ohamub/611/edit - Nick
虽然通过创建函数实例来调用服务。它实际上每个注射器只创建一次,这使它像单身。docs.angularjs.org/guide/dev_guide.services.creating_services - angelokh
如果使用一个明确的实际例子,这个例子可能是不可思议的。我迷失了,想弄清楚事情的重点 toEqual 和 greeter.Greet 是。为什么不使用更真实和相关的东西呢? - Kyle Pennell
使用函数expect()是解释某事的不好选择。下次使用真实世界代码。 - Craig


JS小提琴演示

“你好世界”的例子 factory / service / provider

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

//service style, probably the simplest one
myApp.service('helloWorldFromService', function() {
    this.sayHello = function() {
        return "Hello, World!";
    };
});

//factory style, more involved but more sophisticated
myApp.factory('helloWorldFromFactory', function() {
    return {
        sayHello: function() {
            return "Hello, World!";
        }
    };
});
    
//provider style, full blown, configurable version     
myApp.provider('helloWorld', function() {

    this.name = 'Default';

    this.$get = function() {
        var name = this.name;
        return {
            sayHello: function() {
                return "Hello, " + name + "!";
            }
        }
    };

    this.setName = function(name) {
        this.name = name;
    };
});

//hey, we can configure a provider!            
myApp.config(function(helloWorldProvider){
    helloWorldProvider.setName('World');
});
        

function MyCtrl($scope, helloWorld, helloWorldFromFactory, helloWorldFromService) {
    
    $scope.hellos = [
        helloWorld.sayHello(),
        helloWorldFromFactory.sayHello(),
        helloWorldFromService.sayHello()];
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
    {{hellos}}
</div>
</body>


797
2018-05-15 15:53



不 this 改变中的背景 $get 功能? - 您不再引用该函数中的实例化提供程序。 - Nate-Wilkins
@Nate: this 实际上,并没有改变上下文,因为所谓的是 new Provider()。$ get(),在哪里 Provider 是传递给的函数 app.provider。也就是说 $get() 被称为构造的方法 Provider所以 this 会参考 Provider 正如这个例子所示。 - Brandon
@Brandon哦,那时候那很干净。乍一看令人困惑 - 谢谢你的澄清! - Nate-Wilkins
我为什么要这样做 Unknown provider: helloWorldProvider <- helloWorld 什么时候在本地运行?评论出来,其他两个例子的错误相同。是否有一些隐藏的提供商配置? (Angular 1.0.8) - 发现: stackoverflow.com/questions/12339272/... - Antoine
是@Antoine得到“未知提供:helloWorldProvider”错误的原因,因为在.config代码中,您使用'helloWorldProvider',但是当您在myApp.provider('helloWorld',function())中定义提供程序时,您使用'你好,世界'?换句话说,在您的配置代码中,angular如何知道您指的是helloWorld提供程序?谢谢 - jmtoung


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) 当你使用时 服务,AngularJS使用'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;
  }
});



3)  供应商 是唯一可以传递到.config()函数的服务。如果要在服务对象可用之前为其提供模块范围的配置,请使用提供程序。

app.controller(‘myProvider’, function($scope, myProvider){
  $scope.artist = myProvider.getArtist();
  $scope.data.thingFromConfig = myProvider.thingOnConfig;
});

app.provider(‘myProvider’, function(){
 //Only the next two lines are available in the app.config()
 this._artist = ‘’;
 this.thingFromConfig = ‘’;
  this.$get = function(){
    var that = this;
    return {
      getArtist: function(){
        return that._artist;
      },
      thingOnConfig: that.thingFromConfig
    }
  }
});

app.config(function(myProviderProvider){
  myProviderProvider.thingFromConfig = ‘This was set in config’;
});



非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是我们将调用iTunes的最终完全构建的URL
  • makeUrl是一个创建和返回我们的iTunes友好URL的函数。

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

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

下面 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'服务。然后,我们使用'myFactory'中的数据在$ scope对象上设置属性。上面唯一棘手的代码是你以前从未处理过承诺。因为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 below line creates an object(obj) 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;
}

现在了解'new'关键字在JavaScript中的实际功能,在AngularJS中创建服务应该更容易理解。

创建服务时要了解的最重要的事情是知道服务是使用'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);
      })
  }
});

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

3)提供者

关于Providers最重要的事情是,它们是您可以传递到应用程序的app.config部分的唯一服务。如果您需要更改服务对象的某些部分,然后在应用程序中的其他任何位置可用,那么这一点非常重要。虽然与服务/工厂非常相似,但我们将讨论一些差异。

首先,我们以与服务和工厂类似的方式设置我们的提供商。下面的变量是我们的'私人'和帮助函数。

app.provider('myProvider', function(){
   var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  //Going to set this property on the config function below.
  this.thingFromConfig = ‘’;

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

*如果上述代码的任何部分令人困惑,请查看工厂部分,我在其中解释了它的更多细节。

您可以将提供商视为包含三个部分。第一部分是稍后将修改/设置的“私有”变量/函数(如上所示)。第二部分是app.config函数中可用的变量/函数,因此可以在其他任何地方可用之前进行更改(如上所示)。重要的是要注意这些变量需要附加到'this'关键字。在我们的示例中,只有'thingFromConfig'可以在app.config中进行更改。第三部分(如下所示)是将“myProvider”服务传递到特定控制器时控制器中可用的所有变量/函数。

使用Provider创建服务时,控制器中唯一可用的属性/方法是从$ get()函数返回的属性/方法。下面的代码将$ get放在'this'上(我们知道最终将从该函数返回)。现在,$ get函数返回我们希望在控制器中可用的所有方法/属性。这是一个代码示例。

this.$get = function($http, $q){
    return {
      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: function(artist){
        _artist = artist;
      },
      getArtist: function(){
        return _artist;
      },
      thingOnConfig: this.thingFromConfig
    }
  }

现在完整的Provider代码看起来像这样

app.provider('myProvider', function(){
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  //Going to set this property on the config function below
  this.thingFromConfig = '';

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

  this.$get = function($http, $q){
    return {
      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: function(artist){
        _artist = artist;
      },
      getArtist: function(){
        return _artist;
      },
      thingOnConfig: this.thingFromConfig
    }
  }
});

就像在我们的工厂和服务中一样,setArtist,getArtist和callItunes将在我们通过myProvider的控制器中提供。这是myProvider控制器(几乎与我们的工厂/服务控制器完全相同)。

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

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

  $scope.data.thingFromConfig = myProvider.thingOnConfig;
});

如前所述,使用Provider创建服务的重点是能够在将最终对象传递给应用程序的其余部分之前通过app.config函数更改某些变量。让我们看一个例子。

app.config(function(myProviderProvider){
  //Providers are the only service you can pass into app.config
  myProviderProvider.thingFromConfig = 'This sentence was set in app.config. Providers are the only service that can be passed into config. Check out the code to see how it works';
});

现在你可以看到'thingFromConfig'在我们的提供者中是如何作为空字符串,但当它出现在DOM中时,它将是'这句话被设置......'。


619
2017-12-24 13:15



这篇优秀文章中唯一缺少的部分是在工厂使用服务的相对优势;在Lior接受的答案中清楚地解释了这一点 - infinity
FWIW(也许不多),这是一个博主,它对Angular有疑问,并且不喜欢providerProvider codeofrob.com/entries/you-have-ruined-javascript.html - barlop
“JavaScript大师”的妙语很狡猾。 :D我认为这个答案非常清楚。写得很好。 - amarmishra
您的TLDR需要TLDR。 - JensB
@JensB tl; dr - 学习React。 - Tyler McGinnis


所有服务都是 单身;他们每个应用程序实例化一次。他们可以 任何类型,无论是原始,对象文字,函数,还是自定义类型的实例。

valuefactoryserviceconstant,和 provider 方法都是提供者。他们教Injector如何实例化服务。

最详细,但也是最全面的是提供者   食谱。该 剩下四个 食谱类型 - 价值,工厂,服务和   不变 - 只是提供者食谱上的语法糖

  • 价值食谱 是最简单的情况,您自己实例化服务并提供 实例化的价值 注射器。
  • 工厂配方 为Injector提供了一个工厂函数,它在需要实例化服务时调用。当被召唤时, 工厂功能 创建并返回服务实例。服务的依赖关系作为函数的参数注入。因此,使用此配方可添加以下功能:
    • 能够使用其他服务(具有依赖性)
    • 服务初始化
    • 延迟/延迟初始化
  • 服务配方 几乎与工厂配方相同,但这里注入器调用一个 构造函数 使用new运算符而不是工厂函数。
  • 供应商食谱 通常是 矫枉过正。它允许您配置工厂的创建,从而增加了一层间接。

只有在要公开API时才应使用提供者配方   用于必须在之前进行的应用程序范围的配置   申请开始。这通常只对可重用的有趣   其行为可能需要稍微变化的服务   应用。


506
2018-02-01 12:58



那么服务和工厂基本相同?使用其中一个提供除了替代语法之外的其他内容? - Matt
@Matt,是的,当你已经拥有自己想要作为服务公开的功能时,服务是一种简洁的方式。来自docs:myApp.factory('unicornLauncher',[“apiToken”,function(apiToken){return new UnicornLauncher(apiToken);}]); vs:myApp.service('unicornLauncher',[“apiToken”,UnicornLauncher]); - janek
@joshperry作为一个新手,我已经搜索了服务和工厂之间的差异了一段时间。我同意这是有史以来最好的答案!我将服务理解为服务类(例如编码器/解码器类),它可能具有一些私有属性。而工厂提供了一组无状态辅助方法。 - stanleyxu2005
上面其他答案中的Yaa示例未能非常清楚地解释b / w服务和提供程序的核心差异,这些是在实例化这些配方时注入的内容。 - Ashish Singh


了解AngularJS工厂,服务和提供商

所有这些都用于共享可重用的单例对象。它有助于在您的应用程序/各种组件/模块之间共享可重用的代码。

来自Docs 服务/工厂

  • 懒惰实例化  - Angular仅在应用程序组件依赖它时实例化服务/工厂。
  • 单身  - 每个组件   依赖于服务获取对单个实例的引用   由服务工厂生成。

工厂是在创建对象之前可以操作/添加逻辑的函数,然后返回新创建的对象。

app.factory('MyFactory', function() {
    var serviceObj = {};
    //creating an object with methods/functions or variables
    serviceObj.myFunction = function() {
        //TO DO:
    };
    //return that object
    return serviceObj;
});

用法

它可以只是一个类的函数集合。因此,当您在控制器/工厂/指令功能中注入它时,它可以在不同的控制器中实例化。每个应用程序仅实例化一次。

服务

只需在查看服务时考虑阵列原型。服务是使用“new”关键字实例化新对象的函数。您可以使用以下命令向服务对象添加属性和功能 this关键词。与工厂不同,它不返回任何内容(它返回包含方法/属性的对象)。

app.service('MyService', function() {
    //directly binding events to this context
    this.myServiceFunction = function() {
        //TO DO:
    };
});

用法

需要在整个应用程序中共享单个对象时使用它。例如,经过身份验证的用户详细信息,可共享的方法/数据,实用程序功能等。

提供商

提供程序用于创建可配置的服务对象。您可以从配置功能配置服务设置。它通过使用返回值 $get() 功能。该 $get 函数在角度运行阶段执行。

app.provider('configurableService', function() {
    var name = '';
    //this method can be be available at configuration time inside app.config.
    this.setName = function(newName) {
        name = newName;
    };
    this.$get = function() {
        var getName = function() {
             return name;
        };
        return {
            getName: getName //exposed object to where it gets injected.
        };
    };
});

用法

当您需要在服务对象可用之前为其提供模块化配置时,例如。假设您要根据您的环境设置API URL devstage 要么 prod

注意 

只有提供者将在角度配置阶段可用,而   服务和工厂不是。

希望这已经清除了你的理解 工厂,服务和提供商


221
2017-11-14 06:25



如果我想要一个具有特定接口的服务但有两个不同的实现,并将每个注入到一个控制器但使用ui-router绑定到不同的状态,我该怎么办?例如在一个状态下进行远程调用,但在另一个状态下写入本地存储。提供者文档说要使用 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,所以听起来不可能,对吧? - qix


对我来说,当我意识到他们都以同样的方式工作时,就会发现这个启示:通过运行某些东西 一旦,存储他们获得的价值,然后咳嗽起来 相同的储值 引用时 依赖注入

说我们有:

app.factory('a', fn);
app.service('b', fn);
app.provider('c', fn);

三者之间的区别在于:

  1. a的储值来自于跑步 fn
  2. b的储值来自 newING fn
  3. c的存储价值来自于首先得到一个实例 newING fn,然后运行一个 $get 实例的方法。

这意味着在AngularJS中有类似缓存对象的东西,每次注入的值只分配一次,当它们第一次注入时,其中:

cache.a = fn()
cache.b = new fn()
cache.c = (new fn()).$get()

这就是我们使用的原因 this 在服务中,并定义一个 this.$get 在提供者。


190
2017-07-22 11:39



我最喜欢这个答案。所有这些都是通过DI在需要时提供对对象的访问。通常你做得很好 factory秒。唯一的原因 service存在的是像CoffeeScript,TypeScript,ES6等语言,所以你可以使用他们的类语法。你需要 provider仅当您的模块在多个具有不同设置的应用程序中使用时才使用 app.config()。如果您的服务是纯单例或能够创建某些内容的实例,则仅取决于您的实现。 - Andreas Linnert


服务与提供商与工厂:

我想保持简单。这都是关于基本的JavaScript概念。

首先,我们来谈谈 服务 在AngularJS中!

什么是服务: 在AngularJS中, 服务 只不过是一个单独的JavaScript对象,它可以存储一些有用的方法或属性。此单例对象是基于ngApp(Angular app)创建的,它在当前应用程序中的所有控制器之间共享。当Angularjs实例化服务对象时,它会使用唯一的服务名称注册此服务对象。因此,每当我们需要服务实例时,Angular会在注册表中搜索此服务名称,并返回对服务对象的引用。这样我们就可以在服务对象上调用方法,访问属性等。 您可能有疑问是否也可以在控制器的范围对象上放置属性,方法!那你为什么需要服务对象呢?答案是:服务在多个控制器范围之间共享。如果将某些属性/方法放在控制器的作用域对象中,则它仅可用于当前作用域。但是,当您在服务对象上定义方法,属性时,它将全局可用,并且可以通过注入该服务在任何控制器的范围内访问。

因此,如果有三个控制器范围,让它成为controllerA,controllerB和controllerC,它们将共享相同的服务实例。

<div ng-controller='controllerA'>
    <!-- controllerA scope -->
</div>
<div ng-controller='controllerB'>
    <!-- controllerB scope -->
</div>
<div ng-controller='controllerC'>
    <!-- controllerC scope -->
</div>

如何创建服务?

AngularJS提供了不同的方法来注册服务。在这里,我们将专注于三个方法工厂(..),服务(..),提供者(..);

使用此链接进行代码参考

工厂功能:

我们可以定义一个工厂函数,如下所示。

factory('serviceName',function fnFactory(){ return serviceInstance;})

AngularJS提供 'factory('serviceName',fnFactory)' 带有两个参数,serviceName和JavaScript函数的方法。 Angular通过调用函数来创建服务实例 fnFactory() 如下。

var serviceInstace = fnFactory();

传递的函数可以定义一个对象并返回该对象。 AngularJS只是将此对象引用存储到作为第一个参数传递的变量中。从fnFactory返回的任何内容都将绑定到serviceInstance。我们还可以返回函数,值等,而不是返回对象,无论我们将返回什么,都将可用于服务实例。

例:

var app= angular.module('myApp', []);
//creating service using factory method
app.factory('factoryPattern',function(){
  var data={
    'firstName':'Tom',
    'lastName':' Cruise',
    greet: function(){
      console.log('hello!' + this.firstName + this.lastName);
    }
  };

  //Now all the properties and methods of data object will be available in our service object
  return data;
});

服务功能:

service('serviceName',function fnServiceConstructor(){})

这是另一种方式,我们可以注册一项服务。唯一的区别是AngularJS尝试实例化服务对象的方式。这一次angular使用'new'关键字并调用构造函数,如下所示。

var serviceInstance = new fnServiceConstructor();

在构造函数中,我们可以使用'this'关键字向服务对象添加属性/方法。 例:

//Creating a service using the service method
var app= angular.module('myApp', []);
app.service('servicePattern',function(){
  this.firstName ='James';
  this.lastName =' Bond';
  this.greet = function(){
    console.log('My Name is '+ this.firstName + this.lastName);
  };
});

提供者功能:

Provider()函数是创建服务的另一种方式。让我们有兴趣创建一个只向用户显示一些问候消息的服务。但我们还希望提供一种功能,以便用户可以设置自己的问候消息。在技​​术方面,我们希望创建可配置的服务。我们应该怎么做 ?必须有一种方法,以便app可以传递他们的自定义问候消息,Angularjs将使其可用于创建我们的服务实例的工厂/构造函数。 在这种情况下,provider()函数可以完成这项工作。使用provider()函数我们可以创建可配置的服务。

我们可以使用提供程序语法创建可配置服务,如下所示。

/*step1:define a service */
app.provider('service',function serviceProviderConstructor(){});

/*step2:configure the service */
app.config(function configureService(serviceProvider){});

提供者语法内部如何工作?

1.Provider对象是使用我们在provider函数中定义的构造函数创建的。

var serviceProvider = new serviceProviderConstructor();

2.我们在app.config()中传递的函数,被执行。这称为配置阶段,在这里我们有机会定制我们的服务。

configureService(serviceProvider);

3.通过调用serviceProvider的$ get方法创建最终服务实例。

serviceInstance = serviceProvider.$get()

使用提供语法创建服务的示例代码:

var app= angular.module('myApp', []);
app.provider('providerPattern',function providerConstructor(){
  //this function works as constructor function for provider
  this.firstName = 'Arnold ';
  this.lastName = ' Schwarzenegger' ;
  this.greetMessage = ' Welcome, This is default Greeting Message' ;
  //adding some method which we can call in app.config() function
  this.setGreetMsg = function(msg){
    if(msg){
      this.greetMessage =  msg ;
    }
  };

  //We can also add a method which can change firstName and lastName
  this.$get = function(){
    var firstName = this.firstName;
    var lastName = this.lastName ;
    var greetMessage = this.greetMessage;
    var data={
       greet: function(){
         console.log('hello, ' + firstName + lastName+'! '+ greetMessage);
       }
    };
    return data ;
  };
});

app.config(
  function(providerPatternProvider){
    providerPatternProvider.setGreetMsg(' How do you do ?');
  }
);

工作演示

概要:


 使用返回服务实例的工厂函数。 serviceInstance = fnFactory();

服务 使用构造函数,Angular使用'new'关键字调用此构造函数来创建服务实例。 serviceInstance = new fnServiceConstructor();

提供商 定义了一个providerConstructor函数,这个providerConstructor函数定义了一个工厂函数 $ GET 。 Angular调用$ get()来创建服务对象。提供程序语法具有在实例化之前配置服务对象的附加优势。 serviceInstance = $ get();


133
2017-11-19 13:36





正如几个人在这里正确指出的那样,工厂,提供商,服务,甚至价值和常数都是同一件事的版本。你可以剖析更一般的 provider 进入所有这些。像这样:

enter image description here

这是这张图片来自的文章:

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


79
2017-08-02 05:37