首页 文章

angular.service vs angular.factory

提问于
浏览
1045

我已经看到angular.factory()angular.service()用于申报服务;但是,我在官方文档的任何地方都可以使用cannot find angular.service .

这两种方法有什么区别?应该用什么(假设他们做不同的事情)?

9 回答

  • 2

    TL;DR

    1) 当您使用 Factory 时,您创建了一个对象,向其添加属性,然后返回该对象 . 当您将此工厂传递到控制器时,该对象上的这些属性现在将通过您的工厂在该控制器中可用 .

    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 时,Angular会使用'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;
      }
    });
    

    Non TL;DR

    1) Factory
    工厂是最流行的创建和配置服务的方式 . 真的没有比TL更多的东西了; DR说 . 您只需创建一个对象,向其添加属性,然后返回该对象 . 然后,当您将工厂传递到控制器时,该对象上的这些属性现在将通过您的工厂在该控制器中可用 . 下面是一个更广泛的例子 .

    app.factory('myFactory', function(){
      var service = {};
      return service;
    });
    

    现在,当我们将'myFactory'传递给我们的控制器时,我们可以使用我们附加到'service'的任何属性 .

    现在让我们在回调函数中添加一些“私有”变量 . 这些不能直接从控制器访问,但我们最终会在'service'上设置一些getter / setter方法,以便在需要时能够改变这些'private'变量 .

    app.factory('myFactory', function($http, $q){
      var service = {};
      var baseUrl = 'https://itunes.apple.com/search?term=';
      var _artist = '';
      var _finalUrl = '';
    
      var makeUrl = function(){
       _artist = _artist.split(' ').join('+');
        _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
        return _finalUrl
      }
    
      return service;
    });
    

    在这里你会注意到我们没有将这些变量/函数附加到'service' . 我们只是创建它们以便以后使用或修改它们 .

    • baseUrl是iTunes API所需的基本URL

    • _artist是我们希望查找的艺术家

    • _finalUrl是我们将调用iTunes的最终完全构建的URL makeUrl是一个将创建并返回我们的iTunes友好URL的函数 .

    现在我们的助手/私有变量和函数已经到位,让我们为'service'添加一些属性宾语 . 无论我们采用什么样的“服务”,我们都可以直接使用我们传递给“myFactory”的控制器 .

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

    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'服务 . 然后,我们在$ scope对象上设置属性来自'myFactory'的数据 . 上面唯一棘手的代码是你以前从未处理过承诺 . 因为callItunes正在返回一个promise,所以我们可以使用.then()方法,只有在我们的承诺与iTunes数据一起完成后才设置$ scope.data.artistData . 你会发现我们的控制器很“瘦” . 我们所有的逻辑和持久数据都位于我们的服务中,而不是我们的控制器中 .

    2) Service
    在处理创建服务时,最重要的事情可能是它使用'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 line below this creates an obj object 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;
    }
    

    现在了解“新”关键字在JavaScript中的实际功能,在Angular中创建服务应该更容易理解 .

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

    就像我之前提到的,一旦你真正理解了“新”的含义,服务几乎与Angular的工厂完全相同 .

  • 244
    angular.service('myService', myServiceFunction);
      angular.factory('myFactory', myFactoryFunction);
    

    我无法绕过这个概念,直到我这样对待自己:

    Service :您编写的函数将是 new -ed:

    myInjectedService  <----  new myServiceFunction()
    

    Factory :您编写的函数(构造函数)将为 invoked

    myInjectedFactory  <---  myFactoryFunction()
    

    你用它做什么取决于你,但有一些有用的模式......

    如编写服务函数以公开公共API:

    function myServiceFunction() {
      this.awesomeApi = function(optional) {
        // calculate some stuff
        return awesomeListOfValues;
      }
    }
    ---------------------------------------------------------------------------------
    // Injected in your controller
    $scope.awesome = myInjectedService.awesomeApi();
    

    或使用工厂函数公开公共API:

    function myFactoryFunction() {
      var aPrivateVariable = "yay";
    
      function hello() {
        return "hello mars " + aPrivateVariable;
      }
    
      // expose a public API
      return {
        hello: hello
      };
    }
    ---------------------------------------------------------------------------------
    // Injected in your controller
    $scope.hello = myInjectedFactory.hello();
    

    或使用工厂函数返回构造函数:

    function myFactoryFunction() {
        return function() {
            var a = 2;
            this.a2 = function() {
                return a*2;
            };
        };
    }
    ---------------------------------------------------------------------------------
    // Injected in your controller
    var myShinyNewObject = new myInjectedFactory();
    $scope.four = myShinyNewObject.a2();
    

    使用哪一个?...

    你可以用两者完成同样的事情 . 但是,在某些情况下,工厂会为您提供更灵活的方法来创建具有更简单语法的注入 . 这是因为myInjectedService必须始终是一个对象,myInjectedFactory可以是一个对象,一个函数引用或任何值 . 例如,如果您编写了一个服务来创建构造函数(如上面的上一个示例所示),则必须像这样实例化:

    var myShinyNewObject = new myInjectedService.myFunction()
    

    这可能是不太可取的:

    var myShinyNewObject = new myInjectedFactory();
    

    (但是你应该首先考虑使用这种类型的模式,因为控制器中的新对象会创建难以模拟测试的难以跟踪的依赖关系 . 最好让服务管理一组对象你不要使用 new() 狡猾 . )


    还有一件事,他们都是单身人士......

    另外请记住,在这两种情况下,angular都可以帮助您管理单身人士 . 无论您注入服务或功能的位置或次数,您都将获得对同一对象或功能的相同引用 . (除了工厂只返回一个数字或字符串之类的值 . 在这种情况下,您将始终获得相同的值,但不是引用 . )

  • 1255

    简单的说 ..

    // Service
    service = (a, b) => {
      a.lastName = b;
      return a;
    };
    
    // Factory
    factory = (a, b) => Object.assign({}, a, { lastName: b });
    
    const fullName = { firstName: 'john' };
    
    // Service
    const lastNameService = (a, b) => {
      a.lastName = b;
      return a;
    };
    console.log(lastNameService(fullName, 'doe'));
    
    // Factory
    const lastNameFactory = (a, b) => 
      Object.assign({}, a, { lastName: b })
    console.log(lastNameFactory(fullName, 'doe'));
    
  • 134

    以下是主要差异:

    服务

    语法: module.service( 'serviceName', function );

    结果:将serviceName声明为可注入参数时,将向您提供传递给 module.serviceinstance of a function .

    用法:对于 sharing utility functions 非常有用,只需将 ( ) 附加到注入的函数引用即可调用 . 也可以使用 injectedArg.call( this ) 或类似的方式运行 .

    工厂

    语法: module.factory( 'factoryName', function );

    结果:将factoryName声明为可注入参数时,将向您提供传递给 module.factoryvalue that is returned by invoking the function reference .

    用法:可用于返回 'class' 函数,然后可以将其用于创建实例 .

    这是example using services and factory . 了解更多关于AngularJS Service vs Factory的信息 .

    您还可以在stackoverflow confused about service vs factory上查看AngularJS documentation和类似问题 .

  • 5

    线索在名称中

    服务和工厂彼此相似 . 两者都将产生可以注入其他对象的单个对象,因此通常可以互换使用 .

    它们旨在用于语义上以实现不同的设计模式 .

    服务用于实现服务模式

    服务模式是指将应用程序分解为逻辑上一致的功能单元的模式 . 一个示例可能是API访问器或一组业务逻辑 .

    这在Angular中尤其重要,因为Angular模型通常只是从服务器中提取的JSON对象,因此我们需要在某处放置我们的业务逻辑 .

    这是一个Github服务 . 它知道如何与Github交谈 . 它知道网址和方法 . 我们可以将它注入控制器,它将生成并返回一个promise .

    (function() {
      var base = "https://api.github.com";
    
      angular.module('github', [])
        .service('githubService', function( $http ) {
          this.getEvents: function() {
            var url = [
              base,
              '/events',
              '?callback=JSON_CALLBACK'
            ].join('');
            return $http.jsonp(url);
          }
        });
      )();
    

    工厂实施工厂模式

    另一方面,工厂旨在实施工厂模式 . 我们使用工厂函数生成对象的工厂模式 . 通常我们可能会将其用于构建模型 . 这是一个返回Author构造函数的工厂:

    angular.module('user', [])
      .factory('User', function($resource) {
        var url = 'http://simple-api.herokuapp.com/api/v1/authors/:id'
        return $resource(url);
      })
    

    我们会像这样使用它:

    angular.module('app', ['user'])
      .controller('authorController', function($scope, User) {
        $scope.user = new User();
      })
    

    请注意,工厂也会返回单身人士 .

    工厂可以返回构造函数

    因为工厂只返回一个对象,所以它可以返回你喜欢的任何类型的对象,包括构造函数,如上所示 .

    工厂返回一个物体;服务是新的

    另一个技术差异在于服务和工厂的组成方式 . 将新建服务功能以生成对象 . 将调用工厂函数并返回该对象 .

    • 服务是新的构造函数 .

    • 简单地调用工厂并返回一个对象 .

    这意味着在服务中,我们附加到“this”,它在构造函数的上下文中将指向正在构造的对象 .

    为了说明这一点,这里是使用服务和工厂创建的同一个简单对象:

    angular.module('app', [])
      .service('helloService', function() {
        this.sayHello = function() {
          return "Hello!";
        }
      })
      .factory('helloFactory', function() {
        return {
          sayHello: function() {
            return "Hello!";
          }
        }
      });
    
  • 23

    这里的所有答案似乎都围绕着服务和工厂,并且's valid since that was what was being asked about. But it'也很重要,要记住还有其他几个,包括 provider()value()constant() .

    要记住的关键是每一个都是另一个的特例 . 链中的每个特殊情况允许您使用更少的代码执行相同的操作 . 每个人也有一些额外的限制 .

    要决定何时使用,您只需看到哪一个允许您在更少的代码中执行您想要的操作 . 这是一张图片,说明它们有多相似:

    enter image description here

    有关完整的逐步细分和快速参考何时使用,您可以访问我从以下网站获取此图像的博文:

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

  • 316

    app.factory('fn',fn)与app.service('fn',fn)

    Construction

    对于工厂,Angular将调用该函数来获得结果 . 它是缓存和注入的结果 .

    //factory
     var obj = fn();
     return obj;
    

    使用服务,Angular将通过调用new来调用构造函数 . 构造的函数被缓存并注入 .

    //service
      var obj = new fn();
      return obj;
    

    Implementation

    工厂通常返回一个对象文字,因为返回值是注入控制器,运行块,指令等的内容

    app.factory('fn', function(){
             var foo = 0;
             var bar = 0;
             function setFoo(val) {
                   foo = val;
             }
             function setBar (val){
                   bar = val;
             }
             return {
                    setFoo: setFoo,
                    serBar: setBar
             }
      });
    

    服务功能通常不返回任何内容 . 相反,它们执行初始化和公开函数 . 函数也可以引用'this',因为它是使用'new'构造的 .

    app.service('fn', function () {
             var foo = 0;
             var bar = 0;
             this.setFoo = function (val) {
                   foo = val;
             }
             this.setBar = function (val){
                   bar = val;
             }
    });
    

    Conclusion

    在使用工厂或服务时,它们都非常相似 . 它们被注入到控制器,指令,运行块等中,并以几乎相同的方式在客户端代码中使用 . 它们也都是单例 - 意味着在注入服务/工厂的所有地方之间共享相同的实例 .

    那么哪个你还喜欢吗?任何一个 - 他们是如此相似,以至于差异是微不足道的 . 如果您确实选择了另一个,请注意它们的构造方式,以便您可以正确实现它们 .

  • 33

    我花了一些时间试图找出差异 .

    而且我认为工厂函数使用模块模式和服务函数使用标准的java脚本构造函数模式 .

  • 24

    工厂模式更灵活,因为它可以返回函数和值以及对象 .

    服务模式恕我直言中没有太多意义,因为它所做的一切都可以像工厂一样容易 . 例外情况可能是:

    • 如果由于某种原因关心实例化服务的声明类型 - 如果使用服务模式,则构造函数将是新服务的类型 .

    • 如果你已经有一个你在其他地方使用的构造函数,你也想用作服务(虽然如果你想在其中注入任何东西可能没用多少!) .

    可以说,从语法的角度来看,服务模式是一种稍微好一点的创建新对象的方式,但它确实是真的 - 它无法做到这一点,因为每个服务构造函数都有不同数量的参数 . 实际上角度的作用是在内部使用工厂模式来包装构造函数 . 然后它会做一些聪明的jiggery pokery来模拟javascript的"new"运算符,用可变数量的可注入参数调用你的构造函数 - 但如果你只是直接使用工厂模式就可以省略这一步,从而略微提高代码的效率 .

相关问题