When you start using Angular, you will find that you fill your controllers and scopes with unnecessary logic. You should have realized early on that a controller should be very concise; At the same time most of the business logic and some of the repetitive data should be stored in the service. One day I saw some questions on Stack Overflow saying consider putting repetitive data in controllers, but that’s not what a controller should be for. If memory is needed, controllers should be instantiated when they are needed and cancelled when they are not. So Angular cleans up the current controller every time you switch routes. However, services provide a way for us to store application data for a long time and use services uniformly across different controllers.

Angular gives us three ways to create services:

1. Factory 2. Service 3

First, a brief introduction

When you use factory to create a service, you create an object, add attributes to the object, and return the object. When the service is injected into the controller, the controller can access properties on that object.

app.factory('MyFactory', function () {
        var _artist = '',
            service = {};

        service.getArtist = function () {
            return _artist;
        };

        return service;
    })
    .controller('myFactoryCtrl', [
        '$scope', 'MyFactory',
        function ( $scope, MyFactory ) {
            $scope.artist = MyFactory.getArtist();
        }]);Copy the code

When a service is created using service, it is instantiated using the new keyword. So, all you need to do is add attributes and methods to this, and the service will automatically return this. When the service is injected into the controller, the controller can access properties on that object.

app.service('MyService', function () {
        var _artist = '';
    
        this.getArtist = function () {
            return _artist;
        };
    })
    .controller('myServiceCtrl', [
        '$scope', 'MyService',
        function ( $scope, MyService ) {
            $scope.artist = MyService.getArtist();
        }]);Copy the code

Provider is the only way to create services that can be injected into the config() function. To do some modular configuration before your service starts up, use providers.

App.provider ('MyProvider', function () {this._artist = ''; this.thingFromConfig = ''; $get = function () {var that = this; return { getArtist: function () { return that._artist; }, thingFromConfig: that.thingFromConfig }; }; }) .config(['MyProvider', function ( MyProvider ) { MyProvider.thingFormConfig = 'this is set in config()';  }]) .controller('myProviderCtrl', [ '$scope', 'MyProvider', function ( $scope, MyProvider ) { $scope.artist = MyProvider.getArtist(); }]);Copy the code

Let’s go into details

To illustrate the differences between these three approaches, let’s create the same service using each of them. The service will use the iTunes API and promise’s $Q.

usefactory

The most common way to create and configure a service is to use factory. As explained briefly above, there is not much to say here, except to create an object, add properties and methods to it, and return the object. When the service is injected into the controller, the controller can access properties on that object. A very common example would look something like this.

First we create an object, and then we return it.

app.factory('MyFactory', function () {
    var service = {};
    
    return service;
});Copy the code

Now, any property we add to the service is accessible by injecting MyFactory into the controller.

Now, we’ll add some private properties to the callback function. We won’t be able to access these variables directly from the controller, but we’ll eventually provide getters and setters to the service so that we can modify these properties as needed.

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

You will notice that we did not add these properties and methods to the Service object. We are just creating it briefly for now, so that we can use it or modify it later.

  • BaseUrl is the basic URL required by the iTunes API

  • _artist is the artist we need to find

  • _finalUrl is the URL that ultimately sends the request to iTunes

  • MakeUrl is a function that creates a return to our final URL

Now that our auxiliary variables and functions are created, let’s add some properties to the Service. Any properties we add to the Service are accessible to the controller as long as the service is injected into the controller.

We will create a setArtist() and getArtist() function to set and get the artist’s values. Also, create a function to send requests to iTunes. This function returns a Promise object that will be executed when data is returned from iTunes. If you’re not familiar with Angular promise objects, it’s recommended that you do.

  • SetArtist () takes a parameter and allows values to be set for the artist

  • GetArtist () returns the value of the artist

  • CallITunes () first calls the makeUrl() function to create the URL we need to request using $HTTP, and then uses our final URL to send the request, creating a Promise object. Since $HTTP returns the Promise object, we can call.success and.error after the request. We then process or reject the data returned from iTunes and return an error message such as There was an error.

app.factory('MyFactory', [ '$http', '$q', function ( $http, $q ) { var service = {}, baseUrl = 'https://itunes.apple.com/search?term=', _artist = '', _finalUrl = '';  function makeUrl() { _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 () { var deferred = $q.defer();  _finalUrl = makeUrl(); $http({ method: 'JSONP', url: _finalUrl }).success(function ( data ) { deferred.resolve(data); }).error(function ( error ) { deferred.reject(error);  }); return deferred.promise; }; return service; }]);Copy the code

Now that our service is complete, we can inject the service into any controller and use the methods we added to the service (getArtist, setArtise, callITunes).

app.controller('myFactoryCtrl', [ '$scope', 'MyFactory', 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 ( error ) { alert(error); }); }; }]);Copy the code

In the controller above we inject the MyFactory service and then set the data from the service to the $Scope property. The hardest part of the code above is that you never use promises. Since callITunes() returns a Promise object, we can use the.then() method to set the value of $scope.data.artistData once data is returned from iTunes and the promise executes. You’ll notice that our controller is very simple and all our logical and repetitive data is written in the service.

useservice

Perhaps the most important thing to know when creating a service using service is that it is instantiated using the new keyword. If you’re a Javascript guru, you should know to think in terms of the nature of your code. For programmers who don’t know the Javascript background or who aren’t familiar with what New actually does, we need to review the basics of Javascript to help us ultimately understand the nature of services.

To really see what happens when we call a function with new, let’s create a function and call it with new, and then let’s see what the interpreter does when it finds new. The end result must be the same.

First create our constructor:

function Person( name, age ) {
    this.name = name;
    this.age = age;
}Copy the code

This is a typical constructor. Now, whenever we call this function with new, this will be bound to the newly created object.

Now let’s create a method on the Prototype of Person so that each instance can access it.

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

Now, since we created the sayName function on the prototype of the Person object, this method can be called on every instance of Person.

Now that we have the constructor and prototype methods in place, let’s actually create an instance of Person and call the sayName function:

var tyler = new Person('Tyler', 23);
tyler.sayName();Copy the code

So, in the end, all the code adds up to something like this:

function Person( 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();Copy the code

Now let’s see what happens when we use new. The first thing you should notice is that after using new in our example, we can use Tyler to call the sayName method as if it were an object, which of course Tyler is. So, the first thing we know is that the Person constructor returns an object whether or not we can see it in the code. Second, we should know that the sayName method is defined on the prototype, not directly on the Person object instance, so the object returned by Person must be delegated by the prototype. For a simpler example, when we call Tyler. SayName (), the interpreter says: “OK, I’ll look for the sayName function on the Tyler object I just created and call it. Wait, I don’t see this function, just the name and age properties, so let me check the prototype again. Oh, it’s on the prototype. Let me call it.”

The following code is what you can imagine new actually does in Javascript. The following code is a very basic example where I add some comments from the interpreter’s point of view:

function Person( name, age ) {
    //var obj = object.create(Person.prototype);
    //this = obj;

    this.name = name;
    this.age = age;
    
    //return thisl
}Copy the code

Now that you know what New does, it’s easy to understand how to create a service using a Service.

One of the most important things to know when creating a service using a Service is that it is instantiated using the new keyword. Combined with the above examples, you should realize that you need to add properties and methods to this, and that the service will automatically return this.

Unlike the way we created the service using Factory, we don’t need to create a new object and then return it, because as we mentioned earlier, when we use new, the interpreter automatically creates the object, proxies it to its prototype, and returns it instead of us.

So, before anything else starts, we’ll create our private helper function, much like we did with factory. Now I won’t explain the meaning of each line, but if you’re confused, look at the factory example above.

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

Now, we’re going to add all the methods available to this.

app.service('MyService', [ '$http', '$q', function ( $http, $q ) { var baseUrl = 'https://itunes.apple.com/search?term=', _artist = '', _finalUrl = '';  function makeUrl() { _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 () { var deferred = $q.defer();  _finalUrl = makeUrl(); $http({ method: 'JSONP', url: _finalUrl }).success(function ( data ) { deferred.resolve(data); }).error(function ( error ) { deferred.reject(error);  }); return deferred.promise; }; }]);Copy the code

Now, just as with the service we created with factory, any controller that injects this service can use the setArtist, getArtist, and callITunes methods. Here’s our myServiceCtrl, which is almost identical to myFactoryCtrl.

app.controller('myServiceCtrl', [ '$scope', 'MyService', 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 ( error ) { alert(error); }); }; }]);Copy the code

As I mentioned earlier, service and factory are almost identical once you understand what the new keyword does.

useprovider

The most important thing to remember about providers is that they are the only way to create a service that can be injected into the app.config() function.

This is important if you need to partially configure your service objects before your application runs elsewhere. Although similar to Services and providers, we will explain some of their differences.

First, similarly, we set up our provider. The following variables are our private functions.

app.provider('MyProvider', function () { var baseUrl = 'https://itunes.apple.com/search?term=', _artist = '', _finalUrl = ''; This. thingFromConfig = "; function makeUrl() { _artist = _artist.split(' ').join('+'); _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'; return _finalUrl; }});Copy the code

Again, if you have any questions about the logic of the code above, refer to the previous example.

You can think of a provider as having three parts. The first part is private variables and private functions that can be modified later. The second part is the variables and functions that are accessible in the app.config function, so they can be modified before being used elsewhere. Note that these variables and functions must be appended to this. In our example, the app.config() function can modify only thingFromConfig. The third part is the variables and functions that are accessible in the controller.

When creating a service using a provider, the only properties and methods accessible to the controller are those returned in the $get() function. The following code adds $get to this, and eventually the function is returned.

Now, the $get() function returns the functions and variables we need to access in the controller. Here is an example of code:

this.$get = function ( $http, $q ) {
            return {
                setArtist: function ( artist ) {
                    _artist = artist;
                },
                getArtist: function () {
                    return _artist;
                },
                callITunes: function () {
                    var deferred = $q.defer();
                    _finalUrl = makeUrl();

                    $http({
                        method: 'JSONP',
                        url: _finalUrl
                    }).success(function ( data ) {
                        deferred.resolve(data);
                    }).error(function ( error ) {
                        deferred.reject(error);
                    });

                    return deferred.promise;
                },
                thingOnConfig: this.thingFromConfig
            };
        };Copy the code

Now, the complete provider looks like this:

app.provider('MyProvider', [ '$http', '$q', function ( $http, $q ) { var baseUrl = 'https://itunes.apple.com/search?term=', _artist = '', _finalUrl = ''; this.thingFromConfig = '';  this.$get = function ( $http, $q ) { return { setArtist: function ( artist ) { _artist = artist; }, getArtist: function () { return _artist; }, callITunes: function () { var deferred = $q.defer(); _finalUrl = makeUrl(); $http({ method: 'JSONP', url: _finalUrl }).success(function ( data ) { deferred.resolve(data); }).error(function ( error ) { deferred.reject(error);  }); return deferred.promise; }, thingOnConfig: this.thingFromConfig }; }; function makeUrl() { _artist = _artist.split(' ').join('+');  _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'; return _finalUrl; } }]);Copy the code

Now, like the service and Factory methods before, the corresponding methods are available as long as we inject MyProvider into the controller. Here is myProviderCtrl.

app.controller('myProviderCtrl', [ '$scope', 'MyProvider', 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 ( error ) { alert(error); }); }; $scope.data.thingFromConfig = MyProvider.thingOnConfig; }]);Copy the code

As mentioned earlier, the purpose of using a provider to create a service is to be able to modify some variables through the app.config() function to be passed into the final project. Let’s look at an example:

app.config(['MyProvider', function ( MyProvider ) {
    MyProvider.thingFromConfig = 'This sentence was set in app.config. Providers are the only service that can be passed into app.config. Check out the code to see how it works.';
}]);Copy the code

Now, you can see that in the provider, thingFromConfig is an empty string, but when we display it in the DOM, it will be the string we set above.

Thank you for reading, and I hope it helps you to tell the difference.

To see a complete code example, fork my project: github.com/tylermcginn… Or check out my answer to the question on Stack Overflow