出于內(nèi)存占用和性能的考慮雄妥,控制器只會在需要時被實例化陨界,并且不再需要就會被銷毀。這意味著每次切換路由或重新加載視圖時蘑斧,當前的控制器會被清除掉。
服務提供了一種能在應用的整個生命周期內(nèi)保持數(shù)據(jù)的方法艾帐,它能夠在控制器之間進行通信,并且能保證數(shù)據(jù)的一致性盆偿。服務是一個單例對象柒爸,在每個應用中只會被實例化一次(被$injector
實例化),并且是延遲加載的(需要時才會被創(chuàng)建)事扭。服務提供了把與特定功能相關聯(lián)的方法集中在一起的接口捎稚。
以$http
服務為例,它提供了對瀏覽器的XMLHttpRequest
對象的底層訪問功能求橄,我們可以通過$http
的API同XMLHttpRequest
進行交互今野,而不需要因為調(diào)用這些底層代碼而污染應用。
// 示例服務罐农,在應用的整個生命周期內(nèi)保存current_user
angular.module('myApp', [])
.factory('UserService', function($http) {
var current_user;
return {
getCurrentUser: function() {
return current_user;
},
setCurrentUser: function(user) {
current_user = user;
}
};
});
在AngularJS中創(chuàng)建自己的服務是非常容易的:只需要注冊這個服務即可条霜。服務被注冊后, AngularJS編譯器就可以引用它涵亏,并且在運行時把它當作依賴加載進來宰睡。
注冊一個服務
使用factory API
創(chuàng)建服務,是最常見也是最靈活的方式气筋。
angular.module('myApp.services', [])
.factory('githubService', function() {
var serviceInstance = {};
// 我們的第一個服務
return serviceInstance;
});
服務的工廠函數(shù)用來生成一個單例的對象或函數(shù)拆内,這個對象或函數(shù)就是服務,它會存在于應用的整個生命周期內(nèi)宠默。當我們的AngularJS應用加載服務時麸恍,這個函數(shù)會被執(zhí)行并返回一個單例的服務對象。
同創(chuàng)建控制器的方法一樣搀矫,服務的工廠函數(shù)既可以是一個函數(shù)也可以是一個數(shù)組抹沪。
// 用方括號聲明工廠
angular.module('myApp.services', [])
.factory('githubService', ['$http',function($http) { }]);
例如刻肄,githubService
需要訪問$http
服務,所以我們將$http
服務當作AngularJS應用的一個依賴采够,并將它注入到工廠函數(shù)中肄方。
angular.module('myApp.services',[]).factory('githubService',function($http) {
// 我們的serviceInstance現(xiàn)在可以在函數(shù)定義中訪問$http服務
var serviceInstance = {};
return serviceInstance;
});
現(xiàn)在,無論何處需要訪問GitHub API
都不需要通過$http
來進行了蹬癌,可以通過githubService
來代替权她,并讓它處理所有復雜的業(yè)務邏輯和遠程服務。
GitHub API
提供了一個讀取用戶活動流的方法(活動流就是用戶記錄在GitHub
中的最近的事件列表)逝薪。在我們的服務中隅要,可以創(chuàng)建一個訪問這個API的方法,并將API的請求結(jié)果返回董济。
通過將方法設置為服務對象的一個屬性來將其暴露給外部步清。
angular.module('myApp.services', [])
.factory('githubService', function($http) {
var githubUrl = 'https://api.github.com';
var runUserRequest = function(username, path) {
// 從使用JSONP調(diào)用Github API的$http服務中返回promise
return $http({
method: 'JSONP',
url: githubUrl+'/users/username/' +
path + '?callback=JSON_CALLBACK'
});
};
// 返回帶有一個events函數(shù)的服務對象
return {
events: function(username) {
return runUserRequest(username, 'events');
}
};
});
githubService
中只包含了一個方法,可以在應用的模塊中調(diào)用虏肾。
使用服務
可以在控制器廓啊、指令、過濾器或另外一個服務中通過依賴聲明的方式來使用服務封豪。AngularJS會像平時一樣在運行期自動處理實例化和依賴加載的相關事宜谴轮。
將服務的名字當作參數(shù)傳遞給控制器函數(shù),可以將服務注入到控制器中吹埠。當服務成為了某個控制器的依賴第步,就可以在控制器中調(diào)用任何定義在這個服務對象上的方法。
angular.module('myApp', ['myApp.services'])
.controller('ServiceController',function($scope,githubService) {
// 我們可以調(diào)用對象的事件函數(shù)
$scope.events = githubService.events('auser');
});
githubService
服務已經(jīng)被注入到ServiceController
中缘琅,可以像使用任何其他服務一樣使用它粘都。
<div ng-controller="ServiceController">
<label for="username">Type in a GitHub username</label>
<input type="text" ng-model="username" placeholder="Enter username" />
<ul>
<li ng-repeat="event in events">
<!--
event.actor and event.repo are returned by the github API.
To view the raw API, uncomment the next line:
-->
<!-- {{ event | json }} -->
{{ event.actor.login }} {{ event.repo.name }}
</li>
</ul>
</div>
基于雙向數(shù)據(jù)綁定,我們現(xiàn)在可以通過監(jiān)視$scope.username
來響應視圖中的數(shù)據(jù)變化刷袍。
.controller('ServiceController',function($scope,githubService) {
// 注意username屬性的變化翩隧,如果有變化就運行該函數(shù)
$scope.$watch('username', function(newUsername) {
// 從使用JSONP調(diào)用Github API的$http服務中返回promise
githubService.events(newUsername)
.success(function(data, status, headers) {
// success函數(shù)在數(shù)據(jù)中封裝響應
// 因此我們需要調(diào)用data.data來獲取原始數(shù)據(jù)
$scope.events = data.data;
})
});
});
由于$http
返回的是promise
對象,可以通過.success()
方法像直接調(diào)用$http
一樣調(diào)用返回的對象呻纹。
在這個例子中鸽心,你可能會注意到在輸入字段發(fā)生變化前,有一個延時居暖。如果不延時顽频,將導致輸入字段中的任何一個鍵盤輸入都會讓終端對GitHub API
進行調(diào)用,這顯然不是我們希望的太闺。
通過內(nèi)置服務$timeout
來介紹一下這個延時糯景。在這個例子中$timeout
服務會取消所有網(wǎng)絡請求,并在輸入字段的兩次變化之間延時350ms。換句話說蟀淮,如果用戶兩次輸入之間有350ms的間隔最住,就推斷用戶已經(jīng)完成了輸入,然后開始向GitHub發(fā)送請求怠惶。
app.controller('ServiceController', function($scope,$timeout,githubService) {
var timeout;
$scope.$watch('username', function(newUserName) {
if (newUserName) {
// 如果在進度中有一個超時(timeout)
if (timeout) $timeout.cancel(timeout);
timeout = $timeout(function() {
githubService.events(newUserName).success(function(data,status) {
$scope.events = data.data;
});
}, 350);
}
});
});
使用服務也是在控制器之間共享數(shù)據(jù)的典型方法涨缚。例如,如果我們的應用需要后端服務的授權策治,可以創(chuàng)建一個SessionsService
服務處理用戶的授權過程脓魏,并保存服務端返回的令牌。當應用中任何地方要發(fā)送一個需要授權的請求通惫,可以通過SessionsService
來訪問令牌茂翔。
如果我們的應用中有一個用來設置GitHub用戶名的設置頁面,我們希望在應用中所有的控制器之間共享用戶名履腋。
為了在控制器之間共享數(shù)據(jù)珊燎,需要在服務中添加一個用來儲存用戶名的方法。記住遵湖,服務在應用的生命周期內(nèi)是單例模式的悔政,因此可以將用戶名安全地儲存在其中。
angular.module('myApp.services',[]).factory('githubService',function($http) {
var githubUrl = 'https://api.github.com',githubUsername;
var runUserRequest = function(path) {
// 從使用JSONP的Github API的$http服務中返回promise
return $http({
method: 'JSONP',
url: githubUrl + '/users/githubUsername/' +
path + '?callback=JSON_CALLBACK'
});
};
// 返回帶有兩個方法的服務對象延旧、事件和setUsername
return {
events: function() {
return runUserRequest('events');
},
setUsername: function(username) {
githubUsername = username;
}
};
});
setUsername
方法用來保存當前的GitHub
用戶名了谋国。
githubService
可以注入到應用的任何一個控制器中,并可以在控制器中調(diào)用events()
方法垄潮,且無須擔心當前作用域?qū)ο笊系挠脩裘欠袷钦_的烹卒。
angular.module('myApp',['myApp.services']).controller('ServiceController',
function($scope, githubService) {
$scope.setUsername =githubService.setUsername;
});
創(chuàng)建服務時的設置項
共有5種方法用來創(chuàng)建服務:factory()
闷盔、service()``constant()
弯洗、value()
、provider()
逢勾。
factory()
factory()
方法是創(chuàng)建和配置服務的最快捷方式牡整。factory()
函數(shù)可以接受兩個參數(shù)。
- name(字符串):需要注冊的服務名溺拱。
- getFn(函數(shù)):這個函數(shù)會在AngularJS創(chuàng)建服務實例時被調(diào)用逃贝。
angular.module('myApp').factory('myService',function() {
return {'username': 'auser'};
});
因為服務是單例對象,getFn
在應用的生命周期內(nèi)只會被調(diào)用一次迫摔。同其他服務一樣沐扳,在定義服務時,getFn
可以接受一個包含可被注入對象的數(shù)組或函數(shù)句占。getFn
函數(shù)可以返回簡單類型沪摄、函數(shù)乃至對象等任意類型的數(shù)據(jù)。
angular.module('myApp').factory('githubService',['$http',function($http) {
return {
getUserEvents: function(username) {
// ...
}
};
}]);
service()
使用service()
可以注冊一個支持構(gòu)造函數(shù)的服務,它允許我們?yōu)榉諏ο笞砸粋€構(gòu)造函數(shù)杨拐。
service()
方法接受兩個參數(shù)祈餐。
- name(字符串):要注冊的服務名稱。
- constructor(函數(shù)):構(gòu)造函數(shù)哄陶,我們調(diào)用它來實例化服務對象帆阳。
service()
函數(shù)會在創(chuàng)建實例時通過new
關鍵字來實例化服務對象。
var Person = function($http) {
this.getName = function() {
return $http({ method: 'GET', url: '/api/user'});
};
};
angular.service('personService', Person);
provider()
所有服務工廠都是由$provide
服務創(chuàng)建的屋吨,$provide
服務負責在運行時初始化這些提供者蜒谤。
提供者是一個具有$get()
方法的對象,$injector
通過調(diào)用$get
方法創(chuàng)建服務實例离赫。$provider
提供了數(shù)個不同的API用于創(chuàng)建服務芭逝,每個方法都有各自的特殊用途。
所有創(chuàng)建服務的方法都構(gòu)建在provider
方法之上渊胸。provider()
方法負責在$providerCache
中注冊服務旬盯。
從技術上說,當我們假定傳入的函數(shù)就是$get()
時翎猛,factory()
函數(shù)就是用provider()
方法注冊服務的簡略形式胖翰。
angular.module('myApp').factory('myService',function() {
return {'username': 'auser'};
})
// 這與上面工廠的用法等價
.provider('myService', {
$get: function() {
return {'username': 'auser'};
}
});
同其他創(chuàng)建服務的方法不同,config()
方法可以被注入特殊的參數(shù)切厘。
比如我們希望在應用啟動前配置githubService
的URL:
// 使用`.provider`注冊該服務
angular.module('myApp', [])
.provider('githubService', function($http) {
// 默認的萨咳,私有狀態(tài)
var githubUrl = 'https://github.com'
setGithubUrl: function(url) {
// 通過.config改變默認屬性
if (url) { githubUrl = url }
},
method: JSONP, // 如果需要疫稿,可以重寫
$get: function($http) {
self = this;
return $http({method:self.method,url:githubUrl+'/events'});
}
});
通過使用.provider()
方法培他,可以在多個應用使用同一個服務時獲得更強的擴展性。
在上面的例子中遗座,provider()
方法在文本githubService
后添加Provider
生成了一個新的提供者舀凛,githubServiceProvider
可以被注入到config()
函數(shù)中。
angular.module('myApp', []).config(function(githubServiceProvider) {
githubServiceProvider.setGithubUrl("git@github.com");
});
如果希望在config()
函數(shù)中可以對服務進行配置途蒋,必須用provider()
來定義服務猛遍。
provider()
方法為服務注冊提供者『牌拢可以接受兩個參數(shù)懊烤。
- name(字符串)
name
參數(shù)在providerCache
中是注冊的名字。name+Provider
會成為服務的提供者宽堆。同時name
也是服務實例的名字腌紧。
例如,如果定義了一個githubService
畜隶,那它的提供者就是githubServiceProvider
壁肋。 - aProvider(對象/函數(shù)/數(shù)組)
aProvider
可以是多種形式逮光。
如果aProvider
是函數(shù),那么它會通過依賴注入被調(diào)用墩划,并且負責通過$get
方法返回一個對象涕刚。
如果aProvider
是數(shù)組,會被當做一個帶有行內(nèi)依賴注入聲明的函數(shù)來處理乙帮。數(shù)組的最后一個元素應該是函數(shù)杜漠,可以返回一個帶有$get
方法的對象。
如果aProvider
是對象察净,它應該帶有$get
方法驾茴。
provider()
函數(shù)返回一個已經(jīng)注冊的提供者實例。直接使用provider()
API是最原始的創(chuàng)建服務的方法:
// 在模塊對象上直接創(chuàng)建provider的例子
angular.module('myApp',[]).provider('UserService', {
favoriteColor: null,
setFavoriteColor: function(newColor) {
this.favoriteColor = newColor;
},
// $get函數(shù)可以接受injectables
$get: function($http) {
return {
'name': 'Ari',
getFavoriteColor: function() {
return this.favoriteColor||'unknown';
}
};
}
});
用這個方法創(chuàng)建服務氢卡,必須返回一個定義有$get()
函數(shù)的對象锈至,否則會導致錯誤。
可以通過注入器來實例化服務译秦。
// Get the injector
var injector = angular.injector(['myApp']); // Invoke our service
injector.invoke(
['UserService', function(UserService) {
// UserService returns
// {
// 'name': 'Ari',
// getFavoriteColor: function() {}
// }
}]);
.provider()
是非常強大的峡捡,可以讓我們在不同的應用中共享服務。
constant()
可以將一個已經(jīng)存在的變量值注冊為服務筑悴,并將其注入到應用的其他部分當中们拙。
constant()
函數(shù)可以接受兩個參數(shù)。
- name(字符串):需要注冊的常量的名字阁吝。
- value(常量):需要注冊的常量的值(值或者對象)砚婆。
constant()
方法返回一個注冊后的服務實例。
angular.module('myApp').constant('apiKey','123123123')
這個常量服務可以像其他服務一樣被注入到配置函數(shù)中突勇。
angular.module('myApp').controller('MyController',function($scope,apiKey) {
$scope.apiKey = apiKey;
});
這個常量不能被裝飾器攔截装盯。
value()
如果服務的$get
方法返回的是一個常量,那就沒要必要定義一個包含復雜功能的完整服務甲馋,可以通過value()
函數(shù)方便地注冊服務埂奈。
value()
方法可以接受兩個參數(shù)。
- name(字符串):同樣是需要注冊的服務名摔刁。
- value(值):將這個值將作為可以注入的實例返回挥转。
value()
方法返回以name
參數(shù)的值為名稱的注冊后的服務實例海蔽。
angular.module('myApp').value('apiKey','123123123');
何時使用value()和constant()
value()
方法和constant()
方法之間最主要的區(qū)別是共屈,常量可以注入到配置函數(shù)中,而值不行党窜。
通常情況下拗引,可以通過value()
來注冊服務對象或函數(shù),用constant()
來配置數(shù)據(jù)幌衣。
angular.module('myApp', [])
.constant('apiKey', '123123123')
.config(function(apiKey) {
// 在這里apiKey將被賦值為123123123矾削,就像上面設置的那樣
})
.value('FBid','231231231')
.config(function(FBid) {
// 這將拋出一個錯誤壤玫,未知的provider: FBid
// 因為在config函數(shù)內(nèi)部無法訪問這個值
});
decorator()
$provide
服務提供了在服務實例創(chuàng)建時對其進行攔截的功能,可以對服務進行擴展哼凯,或者用另外的內(nèi)容完全代替它欲间。
裝飾器是非常強大的,它不僅可以應用在我們自己的服務上断部,也可以對AngularJS的核心服務進行攔截猎贴、中斷甚至替換功能的操作。
對服務進行裝飾的場景有很多蝴光,比如對服務進行擴展她渴,將外部數(shù)據(jù)緩存進localStorage
的功能,或者對服務進行封裝以便在開發(fā)中進行調(diào)試和跟蹤等蔑祟。
decorator()
函數(shù)可以接受兩個參數(shù)趁耗。
- name(字符串):將要攔截的服務名稱。
- decoratorFn(函數(shù)):在服務實例化時調(diào)用該函數(shù)疆虚,這個函數(shù)由
injector.invoke
調(diào)用苛败,可以將服務注入這個函數(shù)中。
$delegate
是可以進行裝飾的最原始的服務径簿,為了裝飾其他服務著拭,需要將其注入進裝飾器。
//給githubService添加裝飾器牍帚,從而為每個請求都加上一個時間戳
var githubDecorator = function($delegate,$log) {
var events = function(path) {
var startedAt = new Date();
var events = $delegate.events(path);
// 事件是一個promise
events.finally(function() {
$log.info("Fetching events took"+(new Date()-startedAt)+"ms");
});
return events;
};
return {events: events};
};
angular.module('myApp').config(function($provide) {
$provide.decorator('githubService',githubDecorator);
});