日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

七步从AngularJS菜鸟到专家(6):服务

發(fā)布時間:2025/3/16 javascript 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 七步从AngularJS菜鸟到专家(6):服务 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這是"AngularJS – 七步從菜鳥到專家"系列的第六篇。

在第一篇,我們展示了如何開始搭建一個AngularaJS應(yīng)用。在第五篇我們討論了Angular內(nèi)建的directives。在這一章,我們來討論services,整理我們的代碼并完成我們的音頻播放器應(yīng)用。

通過這整個系列的教程,我們會開發(fā)一個NPR(美國全國公共廣播電臺)廣播的音頻播放器,它能顯示Morning Edition節(jié)目里現(xiàn)在播出的最新故事,并在我們的瀏覽器里播放。完成版的Demo可以看看這里。

目前為止,我們把注意力都放在了如何把視圖綁定到$scope和如何用controller管理數(shù)據(jù),從內(nèi)存和效率角度出 發(fā),controllers僅當(dāng)需要的時候才會被實(shí)例化并在不需要的時候被丟棄掉,這就意味著每一次我們使用route跳轉(zhuǎn)或者重載視圖(我們會在下一篇 討論routing),當(dāng)前的controller會被銷毀。

Services可以讓我們在整個應(yīng)用的生命周期中保存數(shù)據(jù)并且可以讓controllers之間共享數(shù)據(jù)。

第六部分:Services

Services都是單例的,就是說在一個應(yīng)用中,每一個Serice對象只會被實(shí)例化一次(用$injector服務(wù)),主要負(fù)責(zé)提供一個接口把 特定函數(shù)需要的方法放在一起,我們就拿上一章見過的$http Service來舉例,他就提供了訪問底層瀏覽器的XMLHttpRequest對象的方法,相較于調(diào)用底層的XMLHttpRequest對 象,$http API使用起來相當(dāng)?shù)暮唵巍?/p>

Angular內(nèi)建了很多服務(wù)供我們?nèi)粘J褂?#xff0c;這些服務(wù)對于在復(fù)雜應(yīng)用中建立自己的Services都是相當(dāng)有用的。

AngularJS讓我們可以輕松的創(chuàng)建自己的services,僅僅注冊service即可,一旦注冊,Angular編譯器就可以找到并加載他作為依賴供程序運(yùn)行時使用,

最常見的創(chuàng)建方法就是用angular.module API 的factory模式

  • angular.module('myApp.services',?[])?
  • ??.factory('githubService',?function()?{?
  • ????var?serviceInstance?=?{};?
  • ????//?我們的第一個服務(wù)?
  • ????return?serviceInstance;?
  • ??});?
  • 當(dāng)然,我們也可以使用內(nèi)建的$provide service來創(chuàng)建service。

    這個服務(wù)并沒有做實(shí)際的事情,但是他向我們展示了如何去定義一個service。創(chuàng)建一個service就是簡單的返回一個函數(shù),這個函數(shù)返回一個對象。這個對象是在創(chuàng)建應(yīng)用實(shí)例的時候創(chuàng)建的(記住,這個對象是單例對象)

    我們可以在這個縱貫整個應(yīng)用的單例對象里處理特定的需求,在上面的例子中,我們開始創(chuàng)建了GitHub service,

    接下來讓我們添加一些有實(shí)際意義的代碼去調(diào)用GitHub的API:

  • angular.module('myApp.services',?[])?
  • ??.factory('githubService',?['$http',?function($http)?{?
  • ??
  • ????var?doRequest?=?function(username,?path)?{?
  • ??????return?$http({?
  • ????????method:?'JSONP',?
  • ????????url:?'https://api.github.com/users/'?+?username?+?'/'?+?path?+?'?callback=JSON_CALLBACK'?
  • ??????});?
  • ????}?
  • ????return?{?
  • ??????events:?function(username)?{?return?doRequest(username,?'events');?},?
  • ????};?
  • ??}]);?
  • 我們創(chuàng)建了一個只有一個方法的GitHub Service,events可以獲取到給定的GitHub用戶最新的GitHub事件,為了把這個服務(wù)添加到我們的controller中。我們建立一 個controller并加載(或者注入)githubService作為運(yùn)行時依賴,我們把service的名字作為參數(shù)傳遞給controller 函數(shù)(使用中括號[])

  • app.controller('ServiceController',?['$scope',?'githubService',?
  • ????function($scope,?githubService)?{?
  • }]);?
  • 請注意,這種依賴注入的寫法對于js壓縮是安全的,我們會在以后的章節(jié)中深入導(dǎo)論這件事情。

    我們的githubService注入到我們的ServiceController后,我們就可以像使用其他服務(wù)(我們前面提到的$http服務(wù))一樣的使用githubService了。

    我們來修改一下我們的示例代碼,對于我們視圖中給出的GitHub用戶名,調(diào)用GitHub API,就像我們在數(shù)據(jù)綁定第三章節(jié)看到的,我們綁定username屬性到視圖中

  • <div?ng-controller="ServiceController">?
  • ??<label?for="username">Type?in?a?GitHub?username</label>?
  • ??<input?type="text"?ng-model="username"?placeholder="Enter?a?GitHub?username,?like?auser"?/>?
  • ??<pre?ng-show="username">{{?events?}}</pre>?
  • </div>?
  • 現(xiàn)在我們可以監(jiān)視 $scope.username屬性,基于雙向數(shù)據(jù)綁定,只要我們修改了視圖,對應(yīng)的model數(shù)據(jù)也會修改

  • app.controller('ServiceController',?['$scope',?'githubService',?
  • ????function($scope,?githubService)?{?
  • ????//?Watch?for?changes?on?the?username?property.?
  • ????//?If?there?is?a?change,?run?the?function?
  • ????$scope.$watch('username',?function(newUsername)?{?
  • ????????????//?uses?the?$http?service?to?call?the?GitHub?API?
  • ????????????//?and?returns?the?resulting?promise?
  • ??????githubService.events(newUsername)?
  • ????????.success(function(data,?status,?headers)?{?
  • ????????????????????//?the?success?function?wraps?the?response?in?data?
  • ????????????????????//?so?we?need?to?call?data.data?to?fetch?the?raw?data?
  • ??????????$scope.events?=?data.data;?
  • ????????})?
  • ????});?
  • }]);?
  • 因?yàn)榉祷亓?http promise(像我們上一章一樣),我們可以像直接調(diào)用$http service一樣的去調(diào)用.success方法

    ?

    (示例截圖,請前往原文測試)

    在這個示例中,我們注意到輸入框內(nèi)容改變前有一些延遲,如果我們不設(shè)置延遲,那么我們就會對鍵入輸入框的每一個字符調(diào)用GitHub API,這并不是我們想要的,我們可以使用內(nèi)建的$timeout服務(wù)來實(shí)現(xiàn)這種延遲。

    如果想使用$timeout服務(wù),我們只要簡單的把他注入到我們的githubService中就可以了

  • app.controller('ServiceController',?['$scope',?'$timeout',?'githubService',?
  • ????function($scope,?$timeout,?githubService)?{?
  • }]);?
  • 注意我們要遵守Angular services依賴注入的規(guī)范:自定義的service要寫在內(nèi)建的Angular services之后,自定義的service之間是沒有先后順序的。

    我們現(xiàn)在就可以使用$timeout服務(wù)了,在本例中,在輸入框內(nèi)容的改變間隔如果沒有超過350毫秒,$timeout service不會發(fā)送任何網(wǎng)絡(luò)請求。換句話說,如果在鍵盤輸入時超過350毫秒,我們就假定用戶已經(jīng)完成輸入,我們就可以開始向GitHub發(fā)送請求

  • app.controller('ServiceController',?['$scope',?'$timeout',?'githubService',?
  • ??function($scope,?$timeout,?githubService)?{?
  • ????//?The?same?example?as?above,?plus?the?$timeout?service?
  • ????var?timeout;?
  • ????$scope.$watch('username',?function(newVal)?{?
  • ??????if?(newVal)?{?
  • ????????if?(timeout)?$timeout.cancel(timeout);?
  • ????????timeout?=?$timeout(function()?{?
  • ??????????githubService.events(newVal)?
  • ??????????.success(function(data,?status)?{?
  • ????????????$scope.events?=?data.data;?
  • ??????????});?
  • ????????},?350);?
  • ??????}?
  • ????});?
  • ??}]);?
  • 從這應(yīng)用開始,我們只看到了Services是如何把簡單的功能整合在一起,Services還可以在多個controllers之間共享數(shù)據(jù)。比 如,如果我們的應(yīng)用有一個設(shè)置頁面供用戶設(shè)置他們的GitHub username,那么我們就要需要把username與其他controllers共享。

    這個系列的最后一章我們會討論路由以及如何在多頁面中跳轉(zhuǎn)。

    為了在controllers之間共享username,我們需要在service中存儲username,記住,在應(yīng)用的生命周期中Service是一直存在的,所以可以把username安全的存儲在這里

  • angular.module('myApp.services',?[])?
  • ??.factory('githubService',?['$http',?function($http)?{?
  • ????var?githubUsername;?
  • ????var?doRequest?=?function(path)?{?
  • ??????return?$http({?
  • ????????method:?'JSONP',?
  • ????????url:?'https://api.github.com/users/'?+?githubUsername?+?'/'?+?path?+?'?callback=JSON_CALLBACK'?
  • ??????});?
  • ????}?
  • ????return?{?
  • ??????events:?function()?{?return?doRequest('events');?},?
  • ??????setUsername:?function(newUsername)?{?githubUsername?=?newUsername;?}?
  • ????};?
  • ??}]);?
  • 現(xiàn)在,我們的service中有了setUsername方法,方便我們設(shè)置GitHub用戶名,在應(yīng)用的任何controller中,我們都可以調(diào)用events()方法,而根本不用操心在scope對象中的username設(shè)置是否正確。

    我們應(yīng)用里的Services

    在我們的應(yīng)用里,我們需要為3個元素創(chuàng)建對應(yīng)的服務(wù):audio元素,player元素,nprService。最簡單的就是audio service,切記,不要在controller中有任何的操控DOM的行為,如果這么做會污染你的controller并留下潛在的隱患。

    在我們的應(yīng)用中,PlayerController中有一個audio element元素的實(shí)例

  • app.controller('PlayerController',?['$scope',?'$http',?
  • ??function($scope,?$http)?{?
  • ??var?audio?=?document.createElement('audio');?
  • ??$scope.audio?=?audio;?
  • ??//?...?
  • 我們可以建立一個單例audio service,而不是在controller中設(shè)置audio元素

  • app.factory('audio',?['$document',?function($document)?{?
  • ??var?audio?=?$document[0].createElement('audio');?
  • ??return?audio;?
  • }]);?
  • 注意:我們使用了另一個內(nèi)建服務(wù)$document服務(wù),這個服務(wù)就是window.document元素(所有html頁面里javascript的根對象)的引用。

    現(xiàn)在,在我們的PlayController中我們可以引用這個audio元素,而不是在controller中建立這個audio元素

  • app.controller('PlayerController',?['$scope',?'$http',?'audio',?
  • ??function($scope,?$http,?audio)?{?
  • ??$scope.audio?=?audio;?
  • 盡管看起來我們并沒有增強(qiáng)代碼的功能或者讓代碼更加清晰,但是如果有一天,PlayerController不再需要audio service了,我們只需要簡單刪除這個依賴就可以了。到那個時候你就能切身體會到這種代碼寫法的妙處了!

    注意:現(xiàn)在我們可以在其他應(yīng)用中共享audio service了,因?yàn)樗]有綁定特定于本應(yīng)用的功能

    為了看到效果,我們來建立下一個服務(wù): player service,在我們的當(dāng)前循環(huán)中,我們附加了play()和stop()方法到PlayController中。這些方法只跟playing audio有關(guān),所以并沒有必要綁定到PlayController,總之,使用PlayController調(diào)用player service API來操作播放器,而并不需要知道操作細(xì)節(jié)是最好不過的了。

    讓我們來創(chuàng)建player service,我們需要注入我們剛剛創(chuàng)建的還熱乎的audio service 到 player service

  • app.factory('player',?['audio',?function(audio)?{?
  • ??var?player?=?{};?
  • ??return?player;?
  • }]);?
  • 現(xiàn)在我們可以把原先定義在PlayerController中play()方法挪到player service中了,我們還需要添加stop方法并存儲播放器狀態(tài)。

  • app.factory('player',?['audio',?function(audio)?{?
  • ??var?player?=?{?
  • ????playing:?false,?
  • ????current:?null,?
  • ????ready:?false,?
  • ??
  • ????play:?function(program)?{?
  • ??????//?If?we?are?playing,?stop?the?current?playback?
  • ??????if?(player.playing)?player.stop();?
  • ??????var?url?=?program.audio[0].format.mp4.$text;?//?from?the?npr?API?
  • ??????player.current?=?program;?//?Store?the?current?program?
  • ??????audio.src?=?url;?
  • ??????audio.play();?//?Start?playback?of?the?url?
  • ??????player.playing?=?true?
  • ????},?
  • ??
  • ????stop:?function()?{?
  • ??????if?(player.playing)?{?
  • ????????audio.pause();?//?stop?playback?
  • ????????//?Clear?the?state?of?the?player?
  • ????????playerplayer.ready?=?player.playing?=?false;?
  • ????????player.current?=?null;?
  • ??????}?
  • ????}?
  • ??};?
  • ??return?player;?
  • }]);
  • 現(xiàn)在我們已經(jīng)擁有功能完善的play() and stop()方法,我們不需要使用PlayerController來管理跟播放相關(guān)的操作,只需要把控制權(quán)交給PlayController里的player service即可

  • app.controller('PlayerController',?['$scope',?'player',?
  • ??function($scope,?player)?{?
  • ??$scope.player?=?player;?
  • }]);?
  • (注:示例截圖,請到原文測試)

    注意:使用player service的時候,我們不需要去考慮audio service,因?yàn)閜layer會幫我們處理audio service。

    注意:當(dāng)audio播放結(jié)束,我們沒有重置播放器的狀態(tài),播放器會認(rèn)為他自己一直在播放

    為了解決這個問題,我們需要使用$rootScope服務(wù)(另一個Angular的內(nèi)建服務(wù))來捕獲audio元素的ended事件,我們注入$rootScope服務(wù)并創(chuàng)建audio元素的事件監(jiān)聽器

  • app.factory('player',?['audio',?'$rootScope',?
  • ??function(audio,?$rootScope)?{?
  • ??var?player?=?{?
  • ????playing:?false,?
  • ????ready:?true,?
  • ????//?...?
  • ??};?
  • ??audio.addEventListener('ended',?function()?{?
  • ????$rootScope.$apply(player.stop());?
  • ??});?
  • ??return?player;?
  • }]);?
  • 在這種情況下,為了需要捕獲事件而使用了$rootScope service,注意我們調(diào)用了$rootScope.$apply()。 因?yàn)閑nded事件會觸發(fā)外圍Angular event loop.我們會在后續(xù)的文章中討論event loop。

    最后,我們可以獲取當(dāng)前播放節(jié)目的詳細(xì)信息,比如,我們創(chuàng)建一個方法獲取當(dāng)前事件和當(dāng)前audio的播放間隔(我們會用這個參數(shù)顯示當(dāng)前的播放進(jìn)度)。

  • app.factory('player',?['audio',?'$rootScope',?
  • ??function(audio,?$rootScope)?{?
  • ??var?player?=?{?
  • ????playing:?false,?
  • ????//?...?
  • ????currentTime:?function()?{?
  • ??????return?audio.currentTime;?
  • ????},?
  • ????currentDuration:?function()?{?
  • ??????return?parseInt(audio.duration);?
  • ????}?
  • ??}?
  • ??};?
  • ??return?player;?
  • }]);?
  • 在audio元素中存在timeupdate事件,我們可以根據(jù)這個事件更新播放進(jìn)度

  • audio.addEventListener('timeupdate',?function(evt)?{?
  • ????$rootScope.$apply(function()?{?
  • ??????playerplayer.progress?=?player.currentTime();?
  • ??????playerplayer.progress_percent?=?player.progress?/?player.currentDuration();?
  • ????});?
  • ??});?
  • 最后,我們一個添加canplay事件來表示視圖中的audio是否準(zhǔn)備就緒

  • app.factory('player',?['audio',?'$rootScope',?
  • ??function(audio,?$rootScope)?{?
  • ??var?player?=?{?
  • ????playing:?false,?
  • ????ready:?false,?
  • ????//?...?
  • ??}?
  • ??audio.addEventListener('canplay',?function(evt)?{?
  • ????$rootScope.$apply(function()?{?
  • ??????player.ready?=?true;?
  • ????});?
  • ??});?
  • ??return?player;?
  • }]);?
  • 現(xiàn)在,我們有了player service,我們需要操作nprLink directive 來讓播放器 ’play’,而不是用$scope(注意,這么做是可選的,我們也可以在PlayerController中創(chuàng)建play()和stop()方法)

    在directive中,我們需要引用本地scope的player,代碼如下:

  • app.directive('nprLink',?function()?{?
  • ??return?{?
  • ????restrict:?'EA',?
  • ????require:?['^ngModel'],?
  • ????replace:?true,?
  • ????scope:?{?
  • ??????ngModel:?'=',?
  • ??????player:?'='?
  • ????},?
  • ????templateUrl:?'/code/views/nprListItem',?
  • ????link:?function(scope,?ele,?attr)?{?
  • ??????scopescope.duration?=?scope.ngModel.audio[0].duration.$text;?
  • ????}?
  • ??}?
  • });?
  • 現(xiàn)在,為了跟我們已有的模板整合,我們需要更新 index.html的npr-link調(diào)用方式

  • <npr-link?ng-model="program"?player="player"></npr-link>?
  • 在視圖界面,我們調(diào)用play.play(ngModel),而不是play(ngModel).

  • <div?class="nprLink?row"?player="player"?ng-click="player.play(ngModel)">?
  • ??<span?class="name?large-8?columns">?
  • ????<button?class="large-2?small-2?playButton?columns"?ng-click="ngModel.play(ngModel)"><div?class="triangle"></div></button>?
  • ????<div?class="large-10?small-10?columns">?
  • ??????<div?class="row">?
  • ????????<span?class="large-12">{{?ngModel.title.$text?}}</span>?
  • ??????</div>?
  • ??????<div?class="row">?
  • ????????<div?class="small-1?columns"></div>?
  • ????????<div?class="small-2?columns?push-8"><a?href="{{?ngModel.link[0].$text?}}">Link</a></div>?
  • ??????</div>?
  • ????</div>?
  • ??</span>?
  • </div>?
  • 邏輯上,我們需要添加播放器視圖到總體視圖上,因?yàn)槲覀兛梢苑庋bplayer數(shù)據(jù)和狀態(tài)。查看playerView directive?和?template。

    我們來創(chuàng)建最后一個service,nprService,這個service很像 githubService,我們用$http service來獲取NPR的最新節(jié)目

  • app.factory('nprService',?['$http',?function($http)?{?
  • ????var?doRequest?=?function(apiKey)?{?
  • ??????return?$http({?
  • ????????method:?'JSONP',?
  • ????????url:?nprUrl?+?'&apiKey='?+?apiKey?+?'&callback=JSON_CALLBACK'?
  • ??????});?
  • ????}?
  • ??
  • ????return?{?
  • ??????programs:?function(apiKey)?{?return?doRequest(apiKey);?}?
  • ????};?
  • ??}]);?
  • 在PlayerController,我們調(diào)用nprService的programs()(調(diào)用$http service)

  • app.controller('PlayerController',?['$scope',?'nprService',?'player',?
  • ??function($scope,?nprService,?player)?{?
  • ??$scope.player?=?player;?
  • ??nprService.programs(apiKey)?
  • ????.success(function(data,?status)?{?
  • ??????$scope.programs?=?data.list.story;?
  • ????});?
  • }]);?
  • 我們建議使用promises來簡化API,但是為了展示的目的,我們在下一個post會簡單介紹promises。

    當(dāng)PlayerController初始化后,我們的nprService會獲取最新節(jié)目,這樣我們在nprService service中就成功封裝了獲取NPR節(jié)目的功能。另外,我們添加RelatedController在側(cè)邊欄顯示當(dāng)前播放節(jié)目的相關(guān)內(nèi)容。當(dāng)我們的 player service中獲取到最新節(jié)目時,我們將$watc這個player.current屬性并顯示跟這個屬性相關(guān)的內(nèi)容。

  • app.controller('RelatedController',?['$scope',?'player',?
  • ??function($scope,?player)?{?
  • ??$scope.player?=?player;?
  • ??
  • ??$scope.$watch('player.current',?function(program)?{?
  • ????if?(program)?{?
  • ??????$scope.related?=?[];?
  • ??????angular.forEach(program.relatedLink,?function(link)?{?
  • ????????$scope.related.push({?
  • ??????????link:?link.link[0].$text,?
  • ??????????caption:?link.caption.$text?
  • ????????});?
  • ??????});?
  • ????}?
  • ??});?
  • }]);?
  • 在 HTML 代碼中,?we just reference the related links like we did with our NPR programs, using the?ng-repeat?directive:

  • <div?class="large-4?small-4?columns"?ng-controller="RelatedController">?
  • ??<h2>Related?content</h2>?
  • ??<ul?id="related">?
  • ????<li?ng-repeat="s?in?related"><a?href="{{?s.link?}}">{{?s.caption?}}</a></li>?
  • ??</ul>?
  • </div>?
  • 只要player.current內(nèi)容改變,顯示的相關(guān)內(nèi)容也會改變。

    在下一章也是我們的“AngularJS – 七步從菜鳥到專家”的最后一章,我們會討論依賴注入,路由,和產(chǎn)品級別工具來讓我們更快的使用AngularJS

    本系列的官方代碼庫可從github上下載:https://github.com/auser/ng-newsletter-beginner-series.

    要將這個代碼庫保存到本地,請先確保安裝了git,clone此代碼庫,然后check out其中的part6分支:

  • git?clone?https://github.com/auser/ng-newsletter-beginner-series.git?
  • git?checkout?-b?part6?
  • ./bin/server.sh?
  • 總結(jié)

    以上是生活随笔為你收集整理的七步从AngularJS菜鸟到专家(6):服务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。