Angular學習筆記(9)—服務

出于內(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);
});
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末儡遮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子暗赶,更是在濱河造成了極大的恐慌鄙币,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹂随,死亡現(xiàn)場離奇詭異十嘿,居然都是意外死亡,警方通過查閱死者的電腦和手機岳锁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門绩衷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人激率,你說我怎么就攤上這事咳燕。” “怎么了乒躺?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵招盲,是天一觀的道長。 經(jīng)常有香客問我嘉冒,道長曹货,這世上最難降的妖魔是什么咆繁? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮顶籽,結(jié)果婚禮上玩般,老公的妹妹穿的比我還像新娘。我一直安慰自己礼饱,他們只是感情好壤短,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著慨仿,像睡著了一般久脯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上镰吆,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天帘撰,我揣著相機與錄音,去河邊找鬼万皿。 笑死摧找,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的牢硅。 我是一名探鬼主播蹬耘,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼减余!你這毒婦竟也來了综苔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤位岔,失蹤者是張志新(化名)和其女友劉穎如筛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抒抬,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡杨刨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了擦剑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妖胀。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惠勒,靈堂內(nèi)的尸體忽然破棺而出赚抡,到底是詐尸還是另有隱情,我是刑警寧澤捉撮,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布怕品,位于F島的核電站妇垢,受9級特大地震影響巾遭,放射性物質(zhì)發(fā)生泄漏肉康。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一灼舍、第九天 我趴在偏房一處隱蔽的房頂上張望吼和。 院中可真熱鬧,春花似錦骑素、人聲如沸炫乓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽末捣。三九已至,卻和暖如春创橄,著一層夾襖步出監(jiān)牢的瞬間箩做,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工妥畏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留邦邦,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓醉蚁,卻偏偏與公主長得像燃辖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子网棍,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理黔龟,服務發(fā)現(xiàn),斷路器滥玷,智...
    卡卡羅2017閱讀 134,665評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,823評論 6 342
  • 版本:Angular 5.0.0-alpha 依賴注入是重要的應用設計模式捌锭。它使用得非常廣泛,以至于幾乎每個人都稱...
    soojade閱讀 2,992評論 0 3
  • 春去秋來罗捎,花謝花開观谦,看著大一新生稚嫩的臉龐,颯爽的軍裝桨菜,驚覺原來時光竟是這般的悄無聲息豁状,迷迷茫茫,跌跌撞撞倒得,便已在...
    時光涂鴉閱讀 422評論 1 1
  • 夏天一到泻红,讓人最討厭的就是蚊子。睡個午覺霞掺,蚊子在身邊飛來飛去還嗡嗡作響谊路,簡直就想一巴掌扇死它,可是怎么扇都扇不到菩彬,...
    誰的孤獨是一顆眼淚閱讀 431評論 0 1