AngularJS:factory,service与provider的区别

翻译自 http://tylermcginnis.com/angularjs-factory-vs-service-vs-provider/javascript

当你开始使用Angular的时候,你会发现,你老是会让你的控制器和做用域充满各类没必要要的逻辑。你应该早点意识到一个控制器应该是很简洁精炼的;同时大多数的商业逻辑和一些重复性的数据都应该要存储到服务中。一天我在Stack Overflow上看到一些问题说是考虑将重复性的数据放在控制器里,可是,这不是这不是一个控制器应该有的目的。若是为了内存须要,控制器就应该在须要他们的时候实例化,在不须要的时候就取消掉。所以,Angular在你每次切换路由的时候,就会清理当前的控制器。可是呢,服务为咱们提供了一种长期存储应用数据的方式,同时,也能够在不一样的控制器之间统一的使用服务。java

Angular为咱们提供了三种建立服务的方式:git

一、Factory
二、Service
三、Provider程序员

先简单介绍一下

1、当使用factory来建立服务的时候,至关于新建立了一个对象,而后在这个对象上新添属性,最后返回这个对象。当把这个服务注入控制器的时候,控制器就能够访问在那个对象上的属性了。angularjs

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

2、当使用service建立服务的时候,至关于使用new关键词进行了实例化。所以,你只须要在this上添加属性和方法,而后,服务就会自动的返回this。当把这个服务注入控制器的时候,控制器就能够访问在那个对象上的属性了。github

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

3、provider是惟一一种能够建立用来注入到config()函数的服务的方式。想在你的服务启动以前,进行一些模块化的配置的话,就使用providerpromise

app.provider('MyProvider', function () {

        // 只有直接添加在this上的属性才能被config函数访问
        this._artist = '';
        this.thingFromConfig = '';

        // 只有$get函数返回的属性才能被控制器访问
        this.$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();
        }]);

下面咱们来详细说明

为了详细的说明这三种方式的不一样之处,咱们分别使用这三种方式来建立同一个服务。这个服务将会用到iTunes API以及promise的$qapp

使用factoryide

要建立和配置服务,最普通的作法就是使用factory。就像上面简单说明的那样,这里也没有太多要说明的地方,就是建立一个对象,而后为他添加属性和方法,最后返回这个对象。当把这个服务注入控制器的时候,控制器就能够访问在那个对象上的属性了。一个很普通的例子就像下面那样。模块化

首先咱们建立一个对象,而后返回这个对象。

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

如今,咱们添加到service上的任何属性,只要将MyFactory注入到控制器,控制器就均可以访问了。

如今,咱们添加一些私有属性到回调函数里,虽然不能从控制器里直接访问这些变量,可是最终咱们会提供一些gettersetter方法到service上以便于咱们在须要的时候修改这些属性。

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

你应该注意到了,咱们没有把这些属性和方法添加到service对象上去。咱们如今只是先简单的建立出来,以便于待会儿使用或者修改。

  • baseUrl是iTunes API须要的基本URL

  • _artist是咱们须要查找的艺术家

  • _finalUrl是最终向iTunes发送请求的URL

  • makeUrl是一个用来建立返回咱们最终的URL的函数

既然咱们的辅助变量和函数都建立好了,那么,就往service添加一些属性吧。咱们在service上添加的任何属性,只要服务注入了控制器中,那么,控制器就能够访问这些属性。

咱们要建立一个setArtist()getArtist()函数来设置以及取得艺术家的值。同时,也要建立一个用于向iTunes发送请求的函数。这个函数会返回一个promise对象,当有数据从iTunes返回的时候,这个promise对象就会执行。若是你对Angular的promise对象还不是很了解的话,推荐你去深刻了解一下。

  • setArtist()接受一个参数而且容许用来设置艺术家的值

  • getArtist()返回艺术家的值

  • callITunes()首先会调用makeUrl()函数来建立咱们须要使用$http进行请求的URL,而后使用咱们最终的URL来发送请求,建立一个promise对象。因为$http返回了promise对象,咱们就能够在请求以后调用.success.error了。而后咱们处理从iTunes返回的数据或者驳回,并返回一个错误消息,好比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;
    }]);

如今,咱们的服务就完成了,咱们能够将这个服务注入到任何的控制器了,而且,可使用咱们添加到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);
            });
        };
    }]);

在上面的控制器中咱们注入了MyFactory服务,而后,将从服务里来的数据设置到$scope的属性上。上面的代码中最难的地方应该就是你历来没有使用过promise。因为callITunes()返回了一个promise对象,因此一旦有数据从iTunes返回,promise执行的时候,咱们就可使用.then()方法来设置$scope.data.artistData的值了。你会注意到,咱们的控制器很是简洁,咱们全部的逻辑和重复性数据都写在了服务里面。

使用service

也许在使用service建立服务时,咱们须要知道的最重要的一件事就是他是使用new关键字进行实例化的。若是你是Javascript大师,你应该知道从代码的本质来思考。对于那些不了解Javascript背景的或者并不熟悉new实际作了什么的程序员,咱们须要复习一下Javascript的基础知识,以便于最终帮助咱们理解service的本质。

为了真正的看到当咱们使用new来调用函数的时候发生了什么,咱们来建立一个函数,而且使用new来调用他,而后,咱们再看看在解释器发现new的时候,他会作什么。最终结果确定是同样的。

首先建立咱们的构造函数:

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

这是一个典型的构造函数。如今,不管咱们何时使用new来调用这个函数,this都会被绑定到新建立的那个对象上。

如今咱们再在Person的原型上建立一个方法,以便于每个实例均可以访问到。

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

如今,因为咱们在Person对象的原型上建立了sayName函数,因此,Person的每个实例均可以调用到这个方法。

既然咱们已经有了构造函数和原型方法,那么,就来真正的建立一个Person的实例而且调用sayName函数:

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

因此,最终,全部的代码合起来就是下面这个样子:

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

如今咱们来看看在使用new的时候到底发生了什么。首先你应该注意到的是,在咱们的例子中,使用了new以后,咱们可使用tyler来调用sayName方法,就好像这是一个对象同样,固然,tyler确实是一个对象。因此,咱们首先知道的就是不管咱们是否可以在代码里面看见,Person构造函数是会返回一个对象的。第二,咱们咱们应该知道,sayName方法是在原型上的,不是直接定义在Person对象实例上的,因此,Person返回的对象必须是经过原型委托的。用更简单的例子说就是,当咱们调用tyler.sayName()的时候,解释器就会说:“OK,我将会在刚建立的tyler对象上查找sayName函数,而后调用他。等会儿,我没有发现这个函数,只看到了nameage属性,让我再检查一下原型。哦,原来在原型上,让我来调用他”。

下面的代码就是你可以想象的在Javascript里,new实际作了什么。下面的代码是一个很基础的例子,我以解释器的视角来添加了一些注释:

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

    this.name = name;
    this.age = age;
    
    //return thisl
}

如今,既然知道了new作了什么,那么,使用service来建立服务也很容易理解了。

在使用service建立服务时,咱们须要知道的最重要的一件事就是他是使用new关键字进行实例化的。与上面的例子的知识相结合,你应该就能意识到你要把属性和方法添加到this上,而且,服务会自动返回this

与咱们使用factory建立服务的方式不一样,咱们不须要新建立一个对象而后再返回这个对象,由于正如咱们前面所提到的那样,咱们使用new的时候,解释器会自动建立对象,而且代理到他的原型,而后代替咱们返回。

因此,在全部的开始以前,咱们先建立咱们的私有辅助函数,与咱们以前使用factory建立的时候很是相似。如今我不会解释每一行的意义了,若是你有什么疑惑的话,能够看看前面的factory的例子。

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上。

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

如今,就像咱们使用factory所建立的服务那样,注入这个服务的任何一个控制器均可以使用setArtistgetArtistcallITunes方法了。下面是咱们的myServiceCtrl,几乎与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);
            });
        };
    }]);

正如我以前提到的,一旦你理解了new关键词作了什么,servicefactory就几乎是相同的。

使用provider

关于provider,要记住的最重要的一件事就是他是惟一一种能够建立用来注入到app.config()函数的服务的方式。

若是你须要在你的应用在别处运行以前对你的服务对象进行一部分的配置,那么,这个就显得很重要了。尽管与serviceprovider相似,可是咱们仍是会讲解一些他们的不一样之处。

首先,相似的,咱们设置咱们的provider。下面的变量就是咱们的私有函数。

app.provider('MyProvider', function () {
    var baseUrl = 'https://itunes.apple.com/search?term=',
        _artist = '',
        _finalUrl = '';
    
    // 从config函数里设置这个属性
    this.thingFromConfig = '';

    function makeUrl() {
        _artist = _artist.split(' ').join('+');
        _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
        return _finalUrl;
    }
});

再说明一次,若是对上面的代码逻辑有疑问的话,能够参考以前的列子。

你能够认为provider有三个部分,第一部分是私有变量和私有函数,这些变量和函数会在之后被修改。第二部分是在app.config函数里能够访问的变量和函数,因此,他们能够在在其余地方使用以前被修改。注意,这些变量和函数必定要添加到this上面才行。在咱们的例子中,app.config()函数可以修改的只有thingFromConfig。第三部分是在控制器里能够访问的变量和函数。

当使用 provider建立服务的时候,惟一可让控制器访问的属性和方法是在$get()函数里返回的属性和方法。下面的代码将$get添加到了this上面,最终这个函数会被返回。

如今,$get()函数会返回咱们须要在控制器里访问的函数和变量。下面是代码例子:

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

如今,完整的provider就是这个样子:

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

如今,与以前的servicefactory相似,只要咱们把MyProvider注入到控制器里面,对应的方法就可使用了。下面是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;
    }]);

正如以前提到的,使用provider来建立服务的目的就是为了可以经过app.config()函数修改一些变量来传递到最终的项目中。咱们来看个例子:

app.config(['MyProviderProvider', function ( MyProviderProvider ) {
    MyProviderProvider.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.';
}]);

如今,你就能看到,在provider里,thingFromConfig是空字符串,可是,当咱们在DOM里显示的时候,他就会是咱们上面所设置的字符串了。

谢谢你的阅读,但愿可以帮助你分辨这三者的不一样之处。

要查看完整的代码例子,欢迎fork个人项目:https://github.com/tylermcginnis33/AngularServices 或者查看我在Stack Overflow的问题回答

相关文章
相关标签/搜索