AngularJS:服务vs供应商vs工厂

AngularJS中的ServiceProviderFactory有什么区别?


从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的函数引用。

提供者具有可以在模块配置阶段配置的优点。

看到这里提供的代码。

米斯科进一步的解释是:

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

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

JS小提琴演示

factory / service / provider “Hello world”示例:

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)当你使用Service时 ,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)工厂
工厂是创建和配置服务最流行的方式。 DR说的并不比TL多很多。 您只需创建一个对象,为其添加属性,然后返回相同的对象。 然后,当您将工厂传递到您的控制器时,对象上的这些属性现在将通过您的工厂在该控制器中可用。 下面是一个更广泛的例子。

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。
  • 现在我们的助手/私有变量和函数已经到位了,让我们为'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正在返回一个承诺,我们可以使用.then()方法,并且只要我们的承诺满足iTunes数据,就设置$ scope.data.artistData。 你会注意到我们的控制器非常“薄”(这是一个很好的编码习惯)。 我们所有的逻辑和持久数据都位于我们的服务中,而不是我们的控制器中。

    2)服务
    也许在处理创建服务时最重要的事情是,它使用'new'关键字实例化。 对于你的JavaScript专家来说,这应该给你一个关于代码性质的大提示。 对于那些对JavaScript有限背景的人,或者对那些对'新'关键字实际上并不太熟悉的人来说,我们来回顾一些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函数,然后调用它。 等一下,我没看到它 - 我看到的只是名字和年龄,让我检查原型。 是的,看起来像是在原型上,让我称之为。“

    以下是关于如何考虑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中创建Service应该更容易理解。

    在创建服务时要理解的最重要的事情是知道服务使用“新”关键字实例化。 将这些知识与上面的示例结合起来,您现在应该认识到,您将直接将您的属性和方法附加到“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控制器(与我们的工厂控制器几乎完全相同)。

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

    *再次,如果上述代码的任何部分令人困惑,请查看Factory部分,我将解释它所做的更多细节。

    您可以将提供者视为具有三个部分。 第一部分是稍后将修改/设置的'私人'变量/函数(如上所示)。 第二部分是可以在你的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中时,它将是'This sentence was set ...'。

    链接地址: http://www.djcxy.com/p/233.html

    上一篇: AngularJS: Service vs provider vs factory

    下一篇: How do you get a timestamp in JavaScript?