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的問題回答

相關文章
相關標籤/搜索