提问者:小点点

AngularJS:服务vs提供者vs工厂


AngularJS中的服务提供程序工厂之间有什么区别?


共3个答案

匿名用户

从AngularJS邮件列表中,我得到了一个很棒的线程,解释了服务,工厂,提供者以及它们的注入用法。正在汇编答案:

语法:module.service('serviceName',function);
result:将serviceName声明为可注入参数时,将提供该函数的实例。换句话说,New FunctionYouPassedToService()

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

语法:module.provider('provider name',function);
result:将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);
});

然后我们可以像这样在控制器中请求'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');
}

另外,servicefactoryvalue都是从provider派生的。

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

匿名用户

null

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>

匿名用户

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

现在,当我们将'my factory'传递到控制器时,附加到'service'的任何属性都将可用。

现在让我们在回调函数中添加一些“private”变量。这些变量不能直接从控制器中访问,但我们最终将在'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的函数。

现在我们的helper/private变量和函数已经就绪,让我们向'service'对象添加一些属性。我们在“服务”上放入的任何东西都可以直接在我们将“我的工厂”传递到的任何控制器中使用。

我们将创建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;
});

现在我们的工厂完工了。我们现在可以将“我的工厂”注入到任何控制器中,然后就可以调用附加到服务对象(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);
      })
  }
});

在上面的控制器中,我们注入了'My Factory‘服务。然后,我们用来自“My Factory”的数据在$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中实际作用的代码。它基本上是上面段落的一个代码示例。我把“解释器视图”或者解释器看到代码的方式放在Notes里面。

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”上,然后从服务本身返回“this”。让我们来看看这个在行动上。

与我们最初对工厂示例所做的不同,我们不需要创建一个对象然后返回该对象,因为正如前面多次提到的,我们使用了'new'关键字,这样解释器将创建该对象,将其委托给它的原型,然后为我们返回它,而无需我们做这些工作。

首先,让我们创建“private”和helper函数。这应该看起来很熟悉,因为我们做了完全相同的事情与我们的工厂。我不会在这里解释每一行是做什么的,因为我在工厂示例中已经做了,如果您感到困惑,请重新阅读工厂示例。

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

就像我之前提到的,一旦你真正理解了'new'的作用,服务就和AngularJS中的工厂几乎是一样的。

3)提供程序

关于提供者最需要记住的一点是,它们是您可以传递到应用程序的app.config部分的唯一服务。如果您需要在服务对象在应用程序中的其他地方可用之前更改它的某些部分,那么这一点非常重要。虽然与服务/工厂非常相似,但我们将讨论一些不同之处。

首先,我们以类似于我们的服务和工厂的方式来建立我们的供应商。下面的变量是我们的“private”和helper函数。

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中更改。第三部分(如下所示)是当您将'My Provider'服务传入特定控制器时,控制器中可用的所有变量/函数。

当使用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
    }
  }
});

现在就像在我们的工厂和服务中一样,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';
});

现在您可以看到'thing fromconfig‘在我们的提供程序中是如何作为空字符串的,但是当它出现在DOM中时,它将是'this sactured was set…'。