AngularJS:服务vs提供程序vs工厂


3319

什么是之间的差异ServiceProviderFactory在AngularJS?


244
我发现所有Angular术语对初学者都是令人生畏的。我们从这份备忘单开始,这让我们的程序员在学习Angulardemisx.github.io/angularjs/2014/09/14/…时更容易理解。希望这对您的团队也有帮助。
demisx

7
我认为,了解差异的最好方法是使用Angular自己的文档:docs.angularjs.org/guide/providers,它的解释非常好,并使用了一个特殊的示例来帮助您理解它。
拉斐尔·梅林

3
@布莱斯谢谢!根据我在帖子中的评论,我故意将其遗漏,因为我的经验中有99%的用例都可以通过成功处理service.factory。不想进一步使这个主题复杂化。
demisx

3
我觉得这个讨论也非常有用stackoverflow.com/questions/18939709/...
阿南德古普塔

3
这里有一些很好的答案如何servicesfactoriesproviders作品。
Mistalis '16

Answers:


2866

从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负责创造价值的功能也是如此。注意,工厂函数可以要求其他依赖项。

但是,如果您想成为更多面向对象并开设名为Greeter的课程怎么办?

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

然后要实例化,您必须编写

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

然后我们可以像这样在控制器中要求“问候”

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

但这太罗y了。写这个的更短方法是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;
  });
};

58
另请参阅stackoverflow.com/a/13763886/215945,其中讨论了服务和工厂之间的区别。
Mark Rajcok

3
在编辑611中,我添加了角度常数和值的用法。为了证明对方已经显示的差异。jsbin.com/ohamub/611/edit
尼克,

17
尽管通过创建函数实例来调用服务。实际上,每个进样器仅创建一次,这使其像单例。docs.angularjs.org/guide/dev_guide.services.creating_services
angelokh 2013年

33
如果使用明确的实际示例,则该示例可能会令人难以置信。我迷路试图找出事情的点是什么样子toEqual,并greeter.Greet为。为什么不使用稍微真实和相关的内容?
凯尔·潘纳尔

5
使用函数Expect()来解释某些东西是一个糟糕的选择。下次使用真实代码。
克雷格

812

JS小提琴演示

factory/ service/的“ Hello world”示例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>


2
不会this$get函数中更改上下文吗?-您不再在该函数中引用实例化的提供程序。
Nate-Wilkins

12
@Nate:this实际上并不会改变上下文,因为被调用的是new Provider()。$ get(),Provider函数被传递到app.provider。就是说$get()在构造上被称为方法Provider,因此this将参考Provider实例建议。
布兰登

1
@Brandon哦,好,那很好。乍一看令人困惑-感谢您的澄清!
Nate-Wilkins

3
Unknown provider: helloWorldProvider <- helloWorld在本地运行时为什么会得到提示?注释掉,对于其他2个示例相同的错误。是否有一些隐藏的提供程序配置?(角度1.0.8)-找到:stackoverflow.com/questions/12339272/…–
Antoine

4
这就是@Antoine之所以会收到“未知提供:helloWorldProvider”错误的原因,因为在您的.config代码中,您使用的是“ helloWorldProvider”,但是在myApp.provider('helloWorld',function())中定义提供程序时,您会使用'你好,世界'?换句话说,在您的配置代码中,angular如何知道您所指的helloWorld提供程序?谢谢
jmtoung 2014年

645

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”传递到控制器中时,我们附加到“服务”的任何属性都将可用。

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

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;
});

在这里,您会注意到我们没有将这些变量/功能附加到“服务”上。我们只是创建它们,以便以后使用或修改它们。

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

现在我们的帮助器/私有变量和函数已经到位,让我们向“服务”对象添加一些属性。无论我们把“服务”放在哪儿,都可以在传递“ 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正在返回承诺,因此我们可以使用.then()方法,并且仅在iTunes数据满足承诺后,才设置$ scope.data.artistData。您会注意到我们的控制器非常“薄”(这是一个很好的编码实践)。我们所有的逻辑和持久数据都位于我们的服务中,而不是我们的控制器中。

2)服务
也许在创建服务时要知道的最大事情就是使用“ new”关键字实例化了它。对于您的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;
  }

});

现在就像在我们的工厂中一样,无论我们将myService传递给哪个控制器,都可以使用setArtist,getArtist和callItunes。这是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)提供者

关于提供程序,要记住的最大事情是,它们是您可以传递到应用程序的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
    }
  }

现在完整的提供程序代码如下所示

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
    }
  }
});

现在就像在我们的工厂和服务中一样,无论将myProvider传递给哪个控制器,都可以使用setArtist,getArtist和callItunes。这是myProvider控制器(与我们的factory / Service控制器几乎完全相同)。

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中时,它将是'此句子已设置...'。


11
在出色的文章中唯一缺少的部分是在工厂中使用服务的相对优势。Lior接受的答案
无穷大

2
FWIW(可能不多),这是一个博客作者,他对Angular 持怀疑态度
barlop 2015年

3
“ JavaScript专家”的口号很狡猾。:DI认为这个答案非常清楚。写得好极了。
amarmishra

4
您的TLDR需要TLDR。
JensB

3
@JensB tl; dr-学习React
泰勒·麦金尼斯

512

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

valuefactoryserviceconstant,和provider方法都是供应商。他们教注入者如何实例化服务。

提供者配方是最冗长但也是最全面的。在剩下的四个配方类型-价值,工厂,服务,不断- 是在运营商的配方上面只是语法糖

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

    仅当要公开必须在应用程序启动之前进行的应用程序范围配置的API时,才应使用提供者配方。这通常仅对可重复使用的服务感兴趣,这些服务的行为可能需要在应用程序之间稍有不同。

  • 恒配方是一样的价值食谱但它允许您定义是在现有的服务配置阶段。比使用“价值”配方创建的服务要早。与值不同,不能使用来修饰它们decorator
请参阅提供商文档


2
那么服务和工厂本质上是相同的吗?使用其他方法之一除了提供其他语法外没有什么?
马特

2
@Matt,是的,当您已经拥有要作为服务公开的功能时,服务是一种简洁的方法。来自文档:myApp.factory('unicornLauncher',[“ apiToken”,function(apiToken){return new UnicornLauncher(apiToken);}])); vs:myApp.service('unicornLauncher',[“ apiToken”,UnicornLauncher]);
janek

5
@joshperry作为新手,我已经搜索了一段时间的服务和工厂之间的区别。我同意这是有史以来最好的答案!我将服务理解为服务类(例如,编码器/解码器类),它可能具有一些私有属性。并且工厂提供了一组无状态的助手方法。
stanleyxu2005

3
以上其他答案中的Yaa示例无法非常清楚地说明黑白服务和提供者之间的核心区别,即实例化这些配方时注入的内容。
Ashish Singh 2015年

223

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

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

从文档服务/工厂

  • 懒惰地实例化 – 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您的环境像的基础devstageprod

注意

在角度配置阶段仅提供者可用,而服务和工厂则不可用。

希望这能清除您对Factory,Service和Provider的了解。


1
如果我想拥有一个带有特定接口的服务,但是又有两个不同的实现,并且将每个注入到控制器中但使用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 2015年

191

对我来说,当我意识到它们都以相同的方式工作时,就得到了启示:通过运行一次,存储它们获得的值,然后在通过依赖注入进行引用时咳出相同的存储值

说我们有:

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

两者之间的区别在于:

  1. a的储值来自跑步 fn
  2. b的储值来自newingfn
  3. c的存储值来自首先通过newing 获取实例fn,然后运行$get该实例的方法。

这意味着在AngularJS中有一个类似于缓存对象的对象,其每次注入的值仅在第一次注入时才分配一次,其中:

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

这就是为什么我们this在服务中使用并定义this.$getin提供程序的原因。


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

137

服务vs提供者vs工厂:

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

首先,让我们谈谈AngularJS中的服务

什么是服务: 在AngularJS中,服务只是一个可以存储一些有用方法或属性的JavaScript对象。此单例对象是基于ngApp(Angular应用)创建的,并且在当前应用内的所有控制器之间共享。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(..),service(..),provider(..);

使用此链接作为代码参考

工厂功能:

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

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

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

var serviceInstace = fnFactory();

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

例:

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()函数是创建服务的另一种方法。让我们感兴趣的是创建一个仅向用户显示问候消息的服务。但是我们还想提供一种功能,使用户可以设置自己的问候消息。用技术术语来说,我们要创建可配置的服务。我们应该怎么做 ?必须有一种方法,以便应用程序可以传递其自定义的问候消息,而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()

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

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 =新的fnServiceConstructor();

Provider定义了providerConstructor函数,此providerConstructor函数定义了工厂函数$ get。Angular调用$ get()创建服务对象。提供程序语法具有在实例化服务对象之前对其进行配置的另一个优点。 serviceInstance = $ get();



63

您为AngularJS提供了一个函数,当请求工厂时,AngularJS将缓存并注入返回值。

例:

app.factory('factory', function() {
    var name = '';
    // Return value **is** the object that will be injected
    return {
        name: name;
    }
})

用法:

app.controller('ctrl', function($scope, factory) {
     $scope.name = factory.name;
});

服务

您给AngularJS一个函数,AngularJS将调用new来实例化它。AngularJS创建的实例将在请求服务时进行缓存和注入。由于使用new来实例化服务,因此关键字this是有效的并引用实例。

例:

app.service('service', function() {
     var name = '';
     this.setName = function(newName) {
         name = newName;
     }
     this.getName = function() {
         return name;
     }
});

用法:

app.controller('ctrl', function($scope, service) {
   $scope.name = service.getName();
});

提供者

您给AngularJS一个函数,AngularJS将调用它的$get函数。$get当请求服务时,将缓存并注入该函数的返回值。

提供程序允许您 AngularJS调用$get方法以获取注射剂之前配置提供程序。

例:

app.provider('provider', function() {
     var name = '';
     this.setName = function(newName) {
          name = newName;
     }
     this.$get = function() {
         return {
            name: name
         }
     }
})

用法(作为控制器中的注射剂)

app.controller('ctrl', function($scope, provider) {
    $scope.name = provider.name;
});

用法(在$get调用提供者之前配置提供程序以创建可注射对象)

app.config(function(providerProvider) {
    providerProvider.setName('John');
});

56

与提供者一起玩时,我注意到一些有趣的事情。

供应商的注射剂可见性与服务和工厂的可见性不同。如果您声明AngularJS为“常数”(例如myApp.constant('a', 'Robert');),则可以将其注入服务,工厂和提供者。

但是,如果您声明AngularJS“值”(例如。myApp.value('b', {name: 'Jones'});),则可以将其注入服务和工厂,但不能注入提供者创建函数。但是,您可以将其注入$get您为提供程序定义的函数中。AngularJS文档中提到了这一点,但很容易遗漏。您可以在%provide页面上的value和常量方法部分中找到它。

http://jsfiddle.net/R2Frv/1/

<div ng-app="MyAppName">
    <div ng-controller="MyCtrl">
        <p>from Service: {{servGreet}}</p>
        <p>from Provider: {{provGreet}}</p>
    </div>
</div>
<script>
    var myApp = angular.module('MyAppName', []);

    myApp.constant('a', 'Robert');
    myApp.value('b', {name: 'Jones'});

    myApp.service('greetService', function(a,b) {
        this.greeter = 'Hi there, ' + a + ' ' + b.name;
    });

    myApp.provider('greetProvider', function(a) {
        this.firstName = a;
        this.$get = function(b) {
            this.lastName = b.name;
            this.fullName = this.firstName + ' ' + this.lastName;
            return this;
        };
    });

    function MyCtrl($scope, greetService, greetProvider) {
        $scope.servGreet = greetService.greeter;
        $scope.provGreet = greetProvider.fullName;
    }
</script>

45

对于新手来说,这是非常令人困惑的部分,我已尝试用简单的词来澄清它

AngularJS服务:用于与控制器中的服务引用共享实用程序功能。服务本质上是单例的,因此对于一项服务,在浏览器中仅创建一个实例,并且在整个页面中使用相同的引用。

在服务中,我们使用对象将函数名称创建为属性。

AngularJS Factory:Factory的目的也与Service相同,但是在这种情况下,我们创建一个新对象并添加函数作为该对象的属性,最后返回该对象。

AngularJS提供程序:的目的还是相同的,但是Provider给出了$ get函数的输出。

http://www.dotnetfunda.com/articles/show/3156/difference-between-angularjs-service-factory-and-provider中解释和定义了服务,工厂和提供者


2
工厂和提供者也是单例对象吗?在服务方面建议工厂的任何区域?
Sunil Garg

34

对我而言,了解差异的最好和最简单的方法是:

var service, factory;
service = factory = function(injection) {}

AngularJS如何实例化特定组件(简化):

// service
var angularService = new service(injection);

// factory
var angularFactory = factory(injection);

因此,对于服务而言,变成AngularJS组件的是由服务声明函数表示的类的对象实例。对于工厂,它是从工厂声明函数返回的结果。工厂的行为可能与服务相同:

var factoryAsService = function(injection) {
  return new function(injection) {
    // Service content
  }
}

最简单的思维方式是:

  • 服务是一个单例对象实例。如果要为代码提供单例对象,请使用服务。
  • 工厂是一类。如果要为代码提供自定义类,请使用工厂(不能使用服务完成,因为它们已被实例化)。

注释中提供了工厂“类”示例,以及提供程序差异。


如果每次使用时实例化一个服务,如何将其变成单例呢?我可以解决这个问题……
joe 2015年

服务在依赖关系解析期间仅实例化一次,然后当您从注入器请求服务时,您将始终获得同一实例。可以在这里轻松检查:jsfiddle.net/l0co/sovtu55t/1,请在控制台上运行它。控制台显示该服务仅实例化一次。
Lukasz Frankowski

哦,我明白了。我原本希望能够做到字面上的意思new MyService():)
joe 2015年

33

我对此事的澄清:

基本上所有提到的类型(服务,工厂,提供者等)都只是在创建和配置全局变量(对于整个应用程序当然是全局的),就像老式的全局变量一样。

尽管不建议使用全局变量,但这些全局变量的实际用法是通过将变量传递给相关控制器来提供依赖项注入

创建“全局变量”的值有很多复杂程度:

  1. 常量
    常量定义了一个实际常量,在整个应用程序中不应修改该常量,就像其他语言中的常量一样(JavaScript缺少的常量)。

  2. 这是一个可修改的值或对象,它用作某些全局变量,甚至可以在创建其他服务或工厂时注入(请参见有关这些的更多信息)。但是,它必须是“ 文字值 ”,这意味着必须写出实际值,并且不能使用任何计算或编程逻辑(换句话说,可以使用39myText{prop:“ value”}),但是2 +2不是)。
  3. 工厂
    一个更通用的值,可以立即计算。它通过将函数传递给具有计算值所需的逻辑的AngularJS进行工作,然后AngularJS执行该函数,并将返回值保存在命名变量中。
    请注意,可以返回一个对象(在这种情况下,它的功能类似于服务)或一个函数(将作为回调函数保存在变量中)。
  4. 服务
    服务是工厂的简化版本,仅当值是对象时才有效,并且它允许直接在函数中编写任何逻辑(就像它将是构造函数一样),以及声明和访问使用this的对象属性关键字。
  5. 提供程序
    与服务是工厂的简化版本不同,提供程序是一种更复杂但更灵活的初始化“全局”变量的方式,其中最大的灵活性是可以从app.config设置值。通过将具有使用this关键字声明的属性的函数传递给provider,该函数
    就像使用serviceprovider的组合一样工作,可以从中使用。 然后,它需要有一个单独的$ .get函数,该函数由AngularJS在通过文件设置了上述属性后执行,并且此$ .get函数的行为与工厂相同app.config
    app.config 上面的方法是,它的返回值用于初始化“全局”变量。

26

我的理解很简单。

工厂: 您只需在工厂内部创建一个对象并将其返回即可。

服务:

您只有一个使用此关键字定义功能的标准功能。

提供者:

$get您定义了一个对象,它可用于获取返回数据的对象。


您不是把工厂和服务混在一起了吗?服务创造了工厂退货的地方。
Flavien Volken 2014年

当您将服务名称声明为可注入参数时,将为您提供函数实例。换句话说,新的FunctionYouPassedToService()。该对象实例成为AngularJS注册的服务对象,并在以后根据需要注入到其他服务/控制器。// factory将工厂名称声明为可注入参数时,将提供通过调用传递给module.factory的函数引用返回的值。
sajan 2014年

好吧,所以…在角度上,工厂是一个单例,其中“服务”实际上是工厂(按照通用的设计模式而言)
Flavien Volken 2014年

25

Angular文档的摘要:

  • 有五种配方类型定义了如何创建对象:ValueFactoryServiceProviderConstant
  • 工厂服务是最常用的配方。它们之间的唯一区别是,服务配方对自定义类型的对象更有效,而工厂可以生成JavaScript原语和函数。
  • 供应商的食谱是核心配方类型和所有其他的人都在它只是语法糖。
  • 提供者是最复杂的配方类型。除非您要构建需要全局配置的可重用代码段,否则不需要它。

在此处输入图片说明


SO的最佳答案:

https://stackoverflow.com/a/26924234/165673(<- 良好) https://stackoverflow.com/a/27263882/165673
https://stackoverflow.com/a/16566144/165673


20

所有的好答案已经。我想在ServiceFactory上添加更多点。以及服务/工厂之间的差异。还有一个问题,例如:

  1. 我应该使用服务还是工厂?有什么不同?
  2. 他们是相同的行为还是相同的行为?

让我们从服务和工厂之间的区别开始:

  1. 两者都是Singletons:每当Angular第一次将它们作为依赖项时,它都会创建服务/工厂的单个实例。创建实例后,将永远使用同一实例。

  2. 可用于对具有行为的对象进行建模:它们都可以具有方法,内部状态变量等。尽管您编写该代码的方式会有所不同。

服务:

服务是构造函数,Angular将通过调用new实例化它yourServiceName()。这意味着几件事情。

  1. 函数和实例变量将是的属性this
  2. 您不需要返回值。当Angular调用new yourServiceName()时,它将收到this具有您放置在其上的所有属性的对象。

范例范例:

angular.service('MyService', function() {
  this.aServiceVariable = "Ved Prakash"
  this.aServiceMethod = function() {
    return //code
  };
});

当Angular将此MyService服务注入依赖它的控制器时,该控制器将获得一个MyService可以在其上调用函数的函数,例如MyService.aServiceMethod()。

请注意this

由于构造的服务是一个对象,因此调用它时,其中的方法可以引用此对象:

angular.service('ScoreKeeper', function($http) {
  this.score = 0;

  this.getScore = function() {
    return this.score;
  };

  this.setScore = function(newScore) {
    this.score = newScore;
  };

  this.addOne = function() {
    this.score++;
  };
});

您可能会想调用ScoreKeeper.setScore一个Promise链,例如,如果您是通过从服务器获取分数来初始化分数的,则$http.get('/score').then(ScoreKeeper.setScore).该问题是:绑定时ScoreKeeper.setScore会调用thisto,null并且会出错。更好的方法是$http.get('/score').then(ScoreKeeper.setScore.bind(ScoreKeeper))。无论您是否选择在服务方法中使用此功能,请谨慎使用它们。

从中返回值Service

由于JavaScript构造函数的工作方式,如果您(i.e., an Object)constructor函数返回复杂值,则调用者将获取该Object而不是this实例。

这意味着您基本上可以从下面复制粘贴工厂示例,替换factoryservice,它将起作用:

angular.service('MyService', function($http) {
  var api = {};

  api.aServiceMethod= function() {
    return $http.get('/users');
  };
  return api;
});

因此,当Angular使用新的MyService()构造您的服务时,它将获得该api对象而不是MyService实例。

这是任何复杂值(对象,函数)的行为,但不是原始类型的行为。

工厂:

工厂是返回值的普通旧函数。返回值是注入到取决于工厂的事物中的东西。Angular中典型的工厂模式是返回一个具有函数作为属性的对象,如下所示:

angular.factory('MyFactory', function($http) {
  var api = {};

  api.aFactoryMethod= function() {
    return $http.get('/users');
  };

  return api;
});

工厂依赖项的注入值是工厂的返回值,并且不必是对象。这可能是一个功能

以上1和2个问题的答案:

在大多数情况下,只需坚持使用工厂进行所有操作即可。他们的行为更容易理解。没有选择是否返回值,而且,如果做错了事,也不会引入任何错误。

但是,当我谈论将它们作为依赖项注入时,我仍将它们称为“服务”。

服务/工厂的行为非常相似,有些人会说任何一个都可以。确实有些道理,但我发现遵循约翰·帕帕(John Papa)的风格指南的建议更容易,而且坚持使用工厂即可。**


16

另外需要说明的是,工厂可以创建函数/基元,而服务不能。看看这个的jsfiddle基于Epokk的:http://jsfiddle.net/skeller88/PxdSP/1351/

工厂返回可以调用的函数:

myApp.factory('helloWorldFromFactory', function() {
  return function() {
    return "Hello, World!";
  };
});

工厂还可以使用可以调用的方法返回对象:

myApp.factory('helloWorldFromFactory', function() {
  return {
    sayHello: function() {
      return "Hello, World!";
    }
  };
});

该服务返回一个对象,该对象具有可以调用的方法:

myApp.service('helloWorldFromService', function() {
  this.sayHello = function() {
     return "Hello, World!";
  };
});

有关更多详细信息,请参阅我撰写的有关差异的文章:http : //www.shanemkeller.com/tldr-services-vs-factories-in-angular/


16

已经有很好的答案,但是我只想分享这个。

首先:提供者是创建广告的方式/方式service(单个对象)假定由$ injector注入(AngulaJS如何处理IoC模式)。

价值,工厂,服务和常量(4种方式)- 提供者方式/收入之上的语法糖。

Service vs Factory一部分已被覆盖:https : //www.youtube.com/watch?v=BLzNCkPn3ao

服务new实际上是关于关键字的,我们知道它做4件事:

  1. 创建全新的对象
  2. 将其链接到其prototype对象
  3. 连接contextthis
  4. 并返回 this

Factory就是关于Factory Pattern的-包含返回诸如Service之类的对象的函数。

  1. 使用其他服务的能力(具有依赖性)
  2. 服务初始化
  3. 延迟/延迟初始化

这个简单/简短的视频:还介绍了提供者https : //www.youtube.com/watch?v=HvTZbQ_hUZY(您可以看到他们如何从工厂转到提供者)

在应用程序已完全启动/初始化之前,提供者配方主要用于应用程序配置中。


14

在阅读了所有这些帖子之后,它为我带来了更多混乱。.但是仍然所有有价值的信息..最后,我找到了下表,该表将通过简单的比较提供信息

  • 注入器使用配方创建两种类型的对象:服务对象和特殊目的对象
  • 有五种配方类型定义了如何创建对象:值,工厂,服务,提供者和常量。
  • 工厂和服务是最常用的配方。它们之间的唯一区别是,服务配方对于自定义类型的对象更有效,而工厂可以生成JavaScript原语和函数。
  • 提供者配方是核心配方类型,所有其他配方都只是语法糖。
  • 提供者是最复杂的配方类型。除非您要构建需要全局配置的可重用代码段,否则不需要它。
  • 除控制器外的所有特殊目的对象都是通过工厂配方定义的。

在此处输入图片说明

对于初学者来说,这可能不是正确的用例,但总的来说,这是这三个用例的用例。

  1. 如果要在角度模块中使用,则应将配置函数创建为提供程序

angular.module('myApp').config(function($testProvider){
$testProvider.someFunction();
})

  1. Ajax呼叫或第三方集成需要服务
  2. 对于数据操作,将其创建为工厂

对于基本方案,factory&Service的行为相同。


13

这是一些我为AngularjS中的对象工厂编写的代码模板。我以Car / CarFactory为例进行说明。在控制器中编写简单的实现代码。

     <script>
        angular.module('app', [])
            .factory('CarFactory', function() {

                /**
                 * BroilerPlate Object Instance Factory Definition / Example
                 */
                this.Car = function() {

                    // initialize instance properties
                    angular.extend(this, {
                        color           : null,
                        numberOfDoors   : null,
                        hasFancyRadio   : null,
                        hasLeatherSeats : null
                    });

                    // generic setter (with optional default value)
                    this.set = function(key, value, defaultValue, allowUndefined) {

                        // by default,
                        if (typeof allowUndefined === 'undefined') {
                            // we don't allow setter to accept "undefined" as a value
                            allowUndefined = false;
                        }
                        // if we do not allow undefined values, and..
                        if (!allowUndefined) {
                            // if an undefined value was passed in
                            if (value === undefined) {
                                // and a default value was specified
                                if (defaultValue !== undefined) {
                                    // use the specified default value
                                    value = defaultValue;
                                } else {
                                    // otherwise use the class.prototype.defaults value
                                    value = this.defaults[key];
                                } // end if/else
                            } // end if
                        } // end if

                        // update 
                        this[key] = value;

                        // return reference to this object (fluent)
                        return this;

                    }; // end this.set()

                }; // end this.Car class definition

                // instance properties default values
                this.Car.prototype.defaults = {
                    color: 'yellow',
                    numberOfDoors: 2,
                    hasLeatherSeats: null,
                    hasFancyRadio: false
                };

                // instance factory method / constructor
                this.Car.prototype.instance = function(params) {
                    return new 
                        this.constructor()
                                .set('color',           params.color)
                                .set('numberOfDoors',   params.numberOfDoors)
                                .set('hasFancyRadio',   params.hasFancyRadio)
                                .set('hasLeatherSeats', params.hasLeatherSeats)
                    ;
                };

                return new this.Car();

            }) // end Factory Definition
            .controller('testCtrl', function($scope, CarFactory) {

                window.testCtrl = $scope;

                // first car, is red, uses class default for:
                // numberOfDoors, and hasLeatherSeats
                $scope.car1     = CarFactory
                                    .instance({
                                        color: 'red'
                                    })
                                ;

                // second car, is blue, has 3 doors, 
                // uses class default for hasLeatherSeats
                $scope.car2     = CarFactory
                                    .instance({
                                        color: 'blue',
                                        numberOfDoors: 3
                                    })
                                ;
                // third car, has 4 doors, uses class default for 
                // color and hasLeatherSeats
                $scope.car3     = CarFactory
                                    .instance({
                                        numberOfDoors: 4
                                    })
                                ;
                // sets an undefined variable for 'hasFancyRadio',
                // explicitly defines "true" as default when value is undefined
                $scope.hasFancyRadio = undefined;
                $scope.car3.set('hasFancyRadio', $scope.hasFancyRadio, true);

                // fourth car, purple, 4 doors,
                // uses class default for hasLeatherSeats
                $scope.car4     = CarFactory
                                    .instance({
                                        color: 'purple',
                                        numberOfDoors: 4
                                    });
                // and then explicitly sets hasLeatherSeats to undefined
                $scope.hasLeatherSeats = undefined;
                $scope.car4.set('hasLeatherSeats', $scope.hasLeatherSeats, undefined, true);

                // in console, type window.testCtrl to see the resulting objects

            });
    </script>

这是一个简单的例子。我正在使用一些第三方库,它们期望通过不同的对象属性显示“纬度”和“经度”的“位置”对象。我不想破解供应商代码,所以我调整了我传递的“位置”对象。

    angular.module('app')
.factory('PositionFactory', function() {

    /**
     * BroilerPlate Object Instance Factory Definition / Example
     */
    this.Position = function() {

        // initialize instance properties 
        // (multiple properties to satisfy multiple external interface contracts)
        angular.extend(this, {
            lat         : null,
            lon         : null,
            latitude    : null,
            longitude   : null,
            coords: {
                latitude: null,
                longitude: null
            }
        });

        this.setLatitude = function(latitude) {
            this.latitude           = latitude;
            this.lat                = latitude;
            this.coords.latitude    = latitude;
            return this;
        };
        this.setLongitude = function(longitude) {
            this.longitude          = longitude;
            this.lon                = longitude;
            this.coords.longitude   = longitude;
            return this;
        };

    }; // end class definition

    // instance factory method / constructor
    this.Position.prototype.instance = function(params) {
        return new 
            this.constructor()
                    .setLatitude(params.latitude)
                    .setLongitude(params.longitude)
        ;
    };

    return new this.Position();

}) // end Factory Definition

.controller('testCtrl', function($scope, PositionFactory) {
    $scope.position1 = PositionFactory.instance({latitude: 39, longitude: 42.3123});
    $scope.position2 = PositionFactory.instance({latitude: 39, longitude: 42.3333});
}) // end controller

;


12

将此页面和文档(自上次浏览以来,似乎有了很大的改进)作为参考,我整理了以下真实(-ish)世界演示,该演示使用了5种提供者中的4种。值,常量,出厂和完整提供程序。

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);
    };
});

工作演示


12

该答案解决了主题/问题

工厂,服务和常量的用法如何—仅仅是提供者配方上的语法糖?

要么

工厂,服务和提供者如何在内部使用simailar

基本上会发生什么

当你犯了一个factory()它设置你function的第二个参数提供给供应商$get,并返回它(provider(name, {$get:factoryFn })),你得到的是provider,但有总比没有其他属性/方法$get的是provider(意味着你可以不配置此)

工厂源代码

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

进行service()返回时,您需要提供一个factory()function来注入constructor(返回您在服务中提供的构造函数的实例)并返回

服务源代码

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

因此,基本上在两种情况下,您最终都会将提供程序$ get设置为所提供的函数,但是除了$ get之外,您还可以提供其他任何内容,因为您最初可以在provider()中为配置块提供


11

我知道很多很好的答案,但我必须分享使用
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;
... 

10

晚会晚了。但是我认为这对于希望学习(或弄清楚)使用工厂,服务和提供者方法开发Angular JS定制服务的人来说更有用。

我看了这段视频,清楚地说明了开发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");
		});
	};

}]);

定制服务的“提供者”方法的代码(如果您要开发可以配置的服务,则这是必需的):

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>


10

为了澄清起见,您可以从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中处理业务逻辑的三种方式:(受Yaakov的Coursera AngularJS课程的启发

服务内容

句法:

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;
  };
}

}

提供者的特征:

  1. 提供程序是在Angular中创建服务的最灵活的方法。
  2. 我们不仅可以创建可动态配置的工厂,而且在使用工厂时,通过提供者方法,我们可以在整个应用程序的自举中自定义配置工厂一次。
  3. 然后,可以使用自定义设置在整个应用程序中使用工厂。换句话说,我们可以在应用程序启动之前配置此工厂。实际上,在角度文档中提到,当我们使用.service.factory方法配置服务时,provider方法实际上是在后台执行的。
  4. $get是直接连接到提供程序实例的功能。该功能是出厂功能。换句话说,就像我们用来提供给该.factory方法的那个一样。在该功能中,我们创建了自己的服务。$get属性即函数,是提供者成为提供者的原因AngularJS期望提供程序具有$ get属性,其值是Angular将其视为工厂函数的函数。但是,使整个提供程序设置非常特别的原因是,我们可以config在服务提供程序内提供一些对象,并且通常带有默认值,我们以后可以在配置配置整个应用程序的步骤中覆盖这些默认值。

7

工厂:您实际上在工厂内部创建一个对象并将其返回的工厂。
service:您只具有使用this关键字定义功能的标准功能的服务。
provider:提供者,您可以定义一个$ get,它可以用来获取返回数据的对象。


7

本质上,提供者,工厂和服务都是服务。当您需要的只是一个$ get()函数时,工厂就是服务的一种特殊情况,使您可以用更少的代码来编写它。

服务,工厂和提供者之间的主要区别在于它们的复杂性。服务是最简单的形式,工厂则更为健壮,并且提供程序可以在运行时进行配置。

以下是何时使用每种工具的摘要:

工厂:您提供的值需要根据其他数据进行计算。

服务:您正在返回带有方法的对象。

提供者:您希望能够在配置阶段配置要在创建对象之前创建的对象。在应用程序完全初始化之前,主要在应用程序配置中使用提供程序。


嗯 价值,工厂,服务和常量-只是提供者配方上的语法糖。Angularjs文档-提供者
Sudarshan_SMD

是的,我同意,现在角度为4时,我们不再头痛了
eGhoul

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()

在我们的应用程序中使用工厂很容易,因为我们可以在运行时将其简单地注入到需要的地方。

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

-3

语法糖是与众不同的。只需要提供者。或者换句话说,只有提供者是真正的角度,所有其他提供者都是派生的(以减少代码)。还有一个简单的版本,称为Value(),它仅返回值,不返回计算或函数。价值甚至来自提供者!

那么,为什么会有这样的复杂性,为什么我们不能仅仅使用provider却忘记了其他一切呢?它应该可以帮助我们轻松编写代码并更好地沟通。面面俱到的回答是,它变得越复杂,框架就越畅销。


  • 可以返回值=值的提供者
  • 可以实例化并返回的提供程序= Factory(+值)
  • 可以实例化+做某事的提供程序=服务(+工厂,+价值)
  • 提供程序=必须包含一个名为$ get的属性(+ Factory,+ Service,+ Value)

角注入为我们提供了得出这一结论的第一个暗示。

“ $ injector用于检索由提供程序定义的对象实例”,而不是服务,不是工厂而是提供程序。

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

因此,主提供方和注入方都将落在原处:)。当可以通过从IServiceProvider继承在提供程序中实现$ get时,它在Typescript中变得很有趣。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.