angular.service vs angular.factory

后端 未结 9 2125
走了就别回头了
走了就别回头了 2020-11-22 02:50

I have seen both angular.factory() and angular.service() used to declare services; however, I cannot find angular.service anywhere in official documentation.

相关标签:
9条回答
  • 2020-11-22 03:10

    The factory pattern is more flexible as it can return functions and values as well as objects.

    There isn't a lot of point in the service pattern IMHO, as everything it does you can just as easily do with a factory. The exceptions might be:

    • If you care about the declared type of your instantiated service for some reason - if you use the service pattern, your constructor will be the type of the new service.
    • If you already have a constructor function that you're using elsewhere that you also want to use as a service (although probably not much use if you want to inject anything into it!).

    Arguably, the service pattern is a slightly nicer way to create a new object from a syntax point of view, but it's also more costly to instantiate. Others have indicated that angular uses "new" to create the service, but this isn't quite true - it isn't able to do that because every service constructor has a different number of parameters. What angular actually does is use the factory pattern internally to wrap your constructor function. Then it does some clever jiggery pokery to simulate javascript's "new" operator, invoking your constructor with a variable number of injectable arguments - but you can leave out this step if you just use the factory pattern directly, thus very slightly increasing the efficiency of your code.

    0 讨论(0)
  • 2020-11-22 03:17
      angular.service('myService', myServiceFunction);
      angular.factory('myFactory', myFactoryFunction);
    

    I had trouble wrapping my head around this concept until I put it to myself this way:

    Service: the function that you write will be new-ed:

      myInjectedService  <----  new myServiceFunction()
    

    Factory: the function (constructor) that you write will be invoked:

      myInjectedFactory  <---  myFactoryFunction()
    

    What you do with that is up to you, but there are some useful patterns...

    Such as writing a service function to expose a public API:

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

    Or using a factory function to expose a public 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();
    

    Or using a factory function to return a constructor:

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

    Which one to use?...

    You can accomplish the same thing with both. However, in some cases the factory gives you a little bit more flexibility to create an injectable with a simpler syntax. That's because while myInjectedService must always be an object, myInjectedFactory can be an object, a function reference, or any value at all. For example, if you wrote a service to create a constructor (as in the last example above), it would have to be instantiated like so:

    var myShinyNewObject = new myInjectedService.myFunction()
    

    which is arguably less desirable than this:

    var myShinyNewObject = new myInjectedFactory();
    

    (But you should be wary about using this type of pattern in the first place because new-ing objects in your controllers creates hard-to-track dependencies that are difficult to mock for testing. Better to have a service manage a collection of objects for you than use new() wily-nilly.)


    One more thing, they are all Singletons...

    Also keep in mind that in both cases, angular is helping you manage a singleton. Regardless of where or how many times you inject your service or function, you will get the same reference to the same object or function. (With the exception of when a factory simply returns a value like a number or string. In that case, you will always get the same value, but not a reference.)

    0 讨论(0)
  • 2020-11-22 03:18

    TL;DR

    1) When you’re using a Factory you create an object, add properties to it, then return that same object. When you pass this factory into your controller, those properties on the object will now be available in that controller through your 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) When you’re using Service, Angular instantiates it behind the scenes with the ‘new’ keyword. Because of that, you’ll add properties to ‘this’ and the service will return ‘this’. When you pass the service into your controller, those properties on ‘this’ will now be available on that controller through your service.

    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
    Factories are the most popular way to create and configure a service. There’s really not much more than what the TL;DR said. You just create an object, add properties to it, then return that same object. Then when you pass the factory into your controller, those properties on the object will now be available in that controller through your factory. A more extensive example is below.

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

    Now whatever properties we attach to ‘service’ will be available to us when we pass ‘myFactory’ into our controller.

    Now let’s add some ‘private’ variables to our callback function. These won’t be directly accessible from the controller, but we will eventually set up some getter/setter methods on ‘service’ to be able to alter these ‘private’ variables when needed.

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

    Here you’ll notice we’re not attaching those variables/function to ‘service’. We’re simply creating them in order to either use or modify them later.

    • baseUrl is the base URL that the iTunes API requires
    • _artist is the artist we wish to lookup
    • _finalUrl is the final and fully built URL to which we’ll make the call to iTunes makeUrl is a function that will create and return our iTunes friendly URL.

    Now that our helper/private variables and function are in place, let’s add some properties to the ‘service’ object. Whatever we put on ‘service’ we’ll be able to directly use in whichever controller we pass ‘myFactory’ into.

    We are going to create setArtist and getArtist methods that simply return or set the artist. We are also going to create a method that will call the iTunes API with our created URL. This method is going to return a promise that will fulfill once the data has come back from the iTunes API. If you haven’t had much experience using promises in Angular, I highly recommend doing a deep dive on them.

    Below setArtist accepts an artist and allows you to set the artist. getArtist returns the artist callItunes first calls makeUrl() in order to build the URL we’ll use with our $http request. Then it sets up a promise object, makes an $http request with our final url, then because $http returns a promise, we are able to call .success or .error after our request. We then resolve our promise with the iTunes data, or we reject it with a message saying ‘There was an error’.

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

    Now our factory is complete. We are now able to inject ‘myFactory’ into any controller and we’ll then be able to call our methods that we attached to our service object (setArtist, getArtist, and 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);
          })
      }
    });
    

    In the controller above we’re injecting in the ‘myFactory’ service. We then set properties on our $scope object that are coming from data from ‘myFactory’. The only tricky code above is if you’ve never dealt with promises before. Because callItunes is returning a promise, we are able to use the .then() method and only set $scope.data.artistData once our promise is fulfilled with the iTunes data. You’ll notice our controller is very ‘thin’. All of our logic and persistent data is located in our service, not in our controller.

    2) Service
    Perhaps the biggest thing to know when dealing with creating a Service is that that it’s instantiated with the ‘new’ keyword. For you JavaScript gurus this should give you a big hint into the nature of the code. For those of you with a limited background in JavaScript or for those who aren’t too familiar with what the ‘new’ keyword actually does, let’s review some JavaScript fundamentals that will eventually help us in understanding the nature of a Service.

    To really see the changes that occur when you invoke a function with the ‘new’ keyword, let’s create a function and invoke it with the ‘new’ keyword, then let’s show what the interpreter does when it sees the ‘new’ keyword. The end results will both be the same.

    First let’s create our Constructor.

    var Person = function(name, age){
      this.name = name;
      this.age = age;
    }
    

    This is a typical JavaScript constructor function. Now whenever we invoke the Person function using the ‘new’ keyword, ‘this’ will be bound to the newly created object.

    Now let’s add a method onto our Person’s prototype so it will be available on every instance of our Person ‘class’.

    Person.prototype.sayName = function(){
      alert('My name is ' + this.name);
    }
    

    Now, because we put the sayName function on the prototype, every instance of Person will be able to call the sayName function in order alert that instance’s name.

    Now that we have our Person constructor function and our sayName function on its prototype, let’s actually create an instance of Person then call the sayName function.

    var tyler = new Person('Tyler', 23);
    tyler.sayName(); //alerts 'My name is Tyler'
    

    So all together the code for creating a Person constructor, adding a function to it’s prototype, creating a Person instance, and then calling the function on its prototype looks like this.

    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'
    

    Now let’s look at what actually is happening when you use the ‘new’ keyword in JavaScript. First thing you should notice is that after using ‘new’ in our example, we’re able to call a method (sayName) on ‘tyler’ just as if it were an object - that’s because it is. So first, we know that our Person constructor is returning an object, whether we can see that in the code or not. Second, we know that because our sayName function is located on the prototype and not directly on the Person instance, the object that the Person function is returning must be delegating to its prototype on failed lookups. In more simple terms, when we call tyler.sayName() the interpreter says “OK, I’m going to look on the ‘tyler’ object we just created, locate the sayName function, then call it. Wait a minute, I don’t see it here - all I see is name and age, let me check the prototype. Yup, looks like it’s on the prototype, let me call it.”.

    Below is code for how you can think about what the ‘new’ keyword is actually doing in JavaScript. It’s basically a code example of the above paragraph. I’ve put the ‘interpreter view’ or the way the interpreter sees the code inside of notes.

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

    Now having this knowledge of what the ‘new’ keyword really does in JavaScript, creating a Service in Angular should be easier to understand.

    The biggest thing to understand when creating a Service is knowing that Services are instantiated with the ‘new’ keyword. Combining that knowledge with our examples above, you should now recognize that you’ll be attaching your properties and methods directly to ‘this’ which will then be returned from the Service itself. Let’s take a look at this in action.

    Unlike what we originally did with the Factory example, we don’t need to create an object then return that object because, like mentioned many times before, we used the ‘new’ keyword so the interpreter will create that object, have it delegate to it’s prototype, then return it for us without us having to do the work.

    First things first, let’s create our ‘private’ and helper function. This should look very familiar since we did the exact same thing with our factory. I won’t explain what each line does here because I did that in the factory example, if you’re confused, re-read the factory example.

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

    Now, we’ll attach all of our methods that will be available in our controller to ‘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;
      }
    
    });
    

    Now just like in our factory, setArtist, getArtist, and callItunes will be available in whichever controller we pass myService into. Here’s the myService controller (which is almost exactly the same as our factory controller).

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

    Like I mentioned before, once you really understand what ‘new’ does, Services are almost identical to factories in Angular.

    0 讨论(0)
提交回复
热议问题