翻譯自 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()
函數的服務的方式。想在你的服務啓動以前,進行一些模塊化的配置的話,就使用provider
。promise
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的$q
。app
使用factory
ide
要建立和配置服務,最普通的作法就是使用factory
。就像上面簡單說明的那樣,這裏也沒有太多要說明的地方,就是建立一個對象,而後爲他添加屬性和方法,最後返回這個對象。當把這個服務注入控制器的時候,控制器就能夠訪問在那個對象上的屬性了。一個很普通的例子就像下面那樣。模塊化
首先咱們建立一個對象,而後返回這個對象。
app.factory('MyFactory', function () { var service = {}; return service; });
如今,咱們添加到service
上的任何屬性,只要將MyFactory
注入到控制器,控制器就均可以訪問了。
如今,咱們添加一些私有屬性到回調函數裏,雖然不能從控制器裏直接訪問這些變量,可是最終咱們會提供一些getter
和setter
方法到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
函數,而後調用他。等會兒,我沒有發現這個函數,只看到了name
和age
屬性,讓我再檢查一下原型。哦,原來在原型上,讓我來調用他」。
下面的代碼就是你可以想象的在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
所建立的服務那樣,注入這個服務的任何一個控制器均可以使用setArtist
,getArtist
和callITunes
方法了。下面是咱們的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
關鍵詞作了什麼,service
和factory
就幾乎是相同的。
使用provider
關於provider
,要記住的最重要的一件事就是他是惟一一種能夠建立用來注入到app.config()
函數的服務的方式。
若是你須要在你的應用在別處運行以前對你的服務對象進行一部分的配置,那麼,這個就顯得很重要了。儘管與service
和provider
相似,可是咱們仍是會講解一些他們的不一樣之處。
首先,相似的,咱們設置咱們的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; } }]);
如今,與以前的service
和factory
相似,只要咱們把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的問題回答