协慌网

登录 贡献 社区

AngularJS:服务与提供商 vs 工厂

AngularJS 中的ServiceProviderFactory之间有什么区别?

答案

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

服务

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

工厂

语法: module.factory( 'factoryName', function );
结果:当将 factoryName 声明为 injectable 参数时,将通过调用传递给 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');
}

作为旁注, 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' 传递给我们的控制器时,我们可以使用我们附加到'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 中时,它将是 '这句话被设置......'。