七步從AngularJS菜鳥到專家(4和5):指令和表達式
這一篇包含了"AngularJS - 七步從菜鳥到專家"系列的第四篇(指令)和第五篇(表達式)。
之前的幾篇展示了我們應(yīng)用的核心組件,以及如何設(shè)置搭建一個Angular.js應(yīng)用。在這一部分,我們會厘清一些術(shù)語,然后深入探討很多Angular.js提供的核心功能。
通 過這整個系列的教程,我們會開發(fā)一個NPR(美國全國公共廣播電臺)廣播的音頻播放器,它能顯示Morning Edition節(jié)目里現(xiàn)在播出的***故 事,并在我們的瀏覽器里播放它們。完成版的Demo可以看這里(http://www.ng-newsletter.com/code /beginner_series/)
第四部分 指令屬性
目前為止,我們已提到過幾次“指令屬性”的概念,但從未深入探討過它到底是什么。實際上,“指令屬性”就是綁定在DOM元素上的函數(shù),它可以調(diào)用方法、定義行為、綁定controller及$scope對象、操作DOM,等等等等。
當(dāng)瀏覽器啟動、開始解析HTML(像平時一樣)時,DOM元素上的指令屬性就會跟其他屬性一樣被解析。
當(dāng)一個Angular.js應(yīng)用啟動,Angular編譯器就會遍歷DOM樹(從有ng-app指令屬性的那個DOM元素開始,如我們在本系列***篇里所提過的),解析HTML,尋找這些指令屬性函數(shù)。
當(dāng)在一個DOM元素上找到一個或多個這樣的指令屬性函數(shù),它們就會被收集起來、排序,然后按照優(yōu)先級順序被執(zhí)行。
每個指令屬性都有自己的優(yōu)先級,在我們關(guān)于指令屬性的專題文章里(http://www.ng-newsletter.com/posts/directives.html),你可以找到更深入的信息。
Angular.js應(yīng)用的動態(tài)性和響應(yīng)能力,都要歸功于指令屬性。之前我們已經(jīng)看過一些指令屬性的用例:
ng-model
- input ng-model="name" name="Name" placeholder="Enter your name"/>
- <h4>Your name: {{ name }}</h4>
試試看
ng-model指令屬性(我們在之前的章節(jié)使用過它),被用來將DOM文本輸入框的值,跟controller里的$scope model綁定起來。具體的實現(xiàn)過程,是在這個值上綁定了一個$watch函數(shù)(類似JavaScript里的事件監(jiān)聽函數(shù))。
$watch函數(shù)(在使用時)運行在Angular.js的事件循環(huán)(即$digest循環(huán))里,讓Angular.js能夠?qū)OM進行相應(yīng)的更新。請關(guān)注我們關(guān)于$digest循環(huán)的高級文章!
在Angular.js應(yīng)用的開發(fā)中,我們用指令屬性來將行為綁定到DOM上。指令屬性的使用,是一個 應(yīng)用能否擁有動態(tài)性、響應(yīng)能力的關(guān)鍵。Angular.js提供了很多缺省的指令屬性,現(xiàn)在讓我們來看看其中幾個,以及如何使用它們。
幾個常見的指令屬性
{{ 表達式 }}
這個雙大括號指令屬性,使用$watch()函數(shù),給括號內(nèi)的表達式注冊了一個監(jiān)聽器。正是這個$watch函數(shù),讓Angular.js能夠?qū)崟r自動更新view。
那么,到底什么算是個表達式?
第五部分 表達式
要想理解指令屬性的運作,我們必須先理解表達式,所以這里我們就繞個路,看看本系列的第五部分——表達式。在之前的例子里我們已經(jīng)見過表達式,例如 {{ person.name }} 和 {{ clock }}。
***的例子里 (10 * 3.3 | currency) 用了一個過濾器。本系列之后的部分,會深入介紹過濾器。
表達式粗略來看有點像 eval(javascript) 的結(jié)果。它們會經(jīng)過Angular.js的處理,從而擁有以下重要而獨特的性質(zhì):
- 所有表達式都在scope這個context里被執(zhí)行,因此可以使用所有本地 $scope 中的變量。
- 如果一個表達式的執(zhí)行導(dǎo)致類型錯誤或引用錯誤,這些錯誤將不會被拋出。
- 表達式里不允許任何控制函數(shù)流程的功能(如if/else等條件語句)
- 表達式可接受一個或多個串聯(lián)起來的過濾器。
試試看
試試輸入“person“,“clock“或其他數(shù)學(xué)算式如2+4。你甚至可以操作scope,例如,試試輸入person.name = ”Ari”; person.age = 28; person 或 clock
表達式都運行在調(diào)用它們的scope里,所以一個表達式可訪問并操作其scope上的一切。由此,你可以使用表達式遍歷其scope的屬性(我們在ng-repeat中會看到這一應(yīng)用)、調(diào)用scope里的函數(shù),或者對scope中的變量進行數(shù)學(xué)運算。
現(xiàn)在,讓我們回到指令屬性…
ng-init
ng-init指令屬性是一個在啟動時運行的函數(shù)(在程序進入運行階段之前)。它讓我們能夠在程序運行前設(shè)定初始變量的值:
- <b ng-init='name = "Ari Lerner"'>Hello, {{ name }}</b>
試試看
ng-click
ng-click指令屬性給DOM元素注冊了一個點擊事件的監(jiān)聽器。當(dāng)此DOM元素上有點擊事件發(fā)生(即當(dāng)此button或link被點擊時),Angular.js就會執(zhí)行表達式的內(nèi)容,并相應(yīng)地更新view。
- <button ng-click="counter = counter + 1">Add one</button>
- Current counter: {{ counter }}
#p#
試試看
我們也可以用ng-click 來調(diào)用在controller里寫好并綁定在$scope上的函數(shù),例如:
- <button ng-click="sayHello()">Say hello</button>
controller 里的函數(shù):
- app.controller('MyController', function($scope) {
- $scope.sayHello = function() {
- alert("hello!");
- }
- });
試試看
ng-show / ng-hide
The ng-show and ng-hide directives show or hide a portion of the DOM depending on whether the expression is truthy.
ng-show和ng-hide指令,根據(jù)賦予它們的表達式的值的真假性(truthy),來顯示和隱藏它們所屬的那一部分DOM。
我們在這里不會深入,但你應(yīng)該熟悉JavaScript中變量值的“truthy”和“falsy”概念。
- <button ng-init="shouldShow = true" ng-click="shouldShow = !shouldShow">Flip the shouldShow variable</button>
- <div ng-show="shouldShow">
- <h3>Showing {{ shouldShow }}</h3>
- </div> <div ng-hide="shouldShow">
- <h3>Hiding {{ shouldShow }}</h3>
- </div>
試試看
圖6
ng-repeat
ng-repeat指令遍歷一個數(shù)據(jù)集合中的每個數(shù)據(jù)元素,加載HTML模版把數(shù)據(jù)渲染出來。被重復(fù)使用的模版元素,就是我們綁定了這個指令屬性的DOM元素。每一個使用模版渲染的DOM元素都有自己的scope。
在更多的解釋之前,我們先看一個例子。假設(shè)我們的controller里有這樣一個數(shù)據(jù)元素的數(shù)組:
- $scope.roommates = [
- { name: 'Ari'},
- { name: 'Q'},
- { name: 'Sean'},
- { name: 'Anand'}
- ];
在view里我們可以用ng-repeat來遍歷這個集合里的數(shù)據(jù):
- <ul>
- <li ng-repeat="person in roommates">{{ person.name }}</li> </ul>
請看
- Ari
- Q
- Sean
- Anand
對賦予ng-repeat的表達式稍作改動,我們還可以用它遍歷一個由成對的key-value數(shù)據(jù)組成的集合。例如,假設(shè)我們有一個人名和他們最喜歡的顏色的數(shù)據(jù)集合:
- 請看
- Ari
- Q
- Sean
- Anand
要遍歷它,我們可以給ng-repeat指令屬性賦予這個表達式:(key, value) in object:
- <ul>
- <li ng-repeat="(name, color) in people">{{ name }}'s favorite color is {{ color }}
- </li>
- </ul>
請看
- Ari’s favorite color is orange
- Q’s favorite color is blue
- Sean’s favorite color is green
Angular.js提供的直接可用的指令屬性并不多,但它讓我們可以很容易地創(chuàng)建自己的指令屬性。請到這里查看我們的指令屬性創(chuàng)建指南:http://www.ng-newsletter.com/posts/directives.html
我們應(yīng)用中的指令屬性
在上一篇中,我們的收音機應(yīng)用只從NPR API取回了***的音頻節(jié)目列表:
- $scope.programs = data.list.story;
現(xiàn)在我們學(xué)了遍歷一個list的實現(xiàn)方法,可以在我們的收音機應(yīng)用里,像剛才那樣用ng-repeat來遍歷這個節(jié)目列表了:
- <ul id="programs_list" class="">
- <li ng-repeat="program in programs">
- <span class="large-12">{{ program.title.$text }}</span>
- </li>
- </ul>
#p#
NPR API給我們的是一個有title+$text的列表,這個結(jié)構(gòu)是NPR API所特有的,而不是Angular.js的。
現(xiàn)在我們列出了節(jié)目和它們的標(biāo)題,但還不能點擊并播放它們。用ng-click我們可以給HTML元素加上一個點擊功能:
- <ul id="programs_list" class="">
- <li ng-repeat="program in programs" ng-click="play(program)">
- <span class="large-12">{{ program.title.$text }}</span>
- </li>
- </ul>
通過這一步,我們把一個play動作函數(shù)綁定到了列表里的<li>DOM元素上?,F(xiàn)在,我們在PlayerController里創(chuàng)建這個play動作函數(shù),然后我們就有了一個功能完備的音頻應(yīng)用:
- // format.mp4.$text是NPR API給我們的到這個音頻mp4文件的路徑 $scope.play = function(program) {
- if ($scope.playing) audio.pause();
- var url = program.audio[0].format.mp4.$text;
- audio.src = url;
- audio.play();
- // 儲存播放器的狀態(tài)為正在播放
- $scope.playing = true;
- }
現(xiàn)在這個應(yīng)用功能完備了,但是還不太好看。而且隨著我們繼續(xù)添加新功能,代碼也會膨脹,變得難以管理。我們可以創(chuàng)建自己的指令屬性,來幫助我們減少復(fù)雜性。
想更多地學(xué)習(xí)自定義指令屬性,可以看看我們深入探討指令屬性的文章:http://www.ng-newsletter.com/posts/directives.html
創(chuàng)建自定義指令屬性,我們使用app對象的directive方法:
- app.directive('nprLink', function() {
- return {
- restrict: 'EA',
- require: ['^ngModel'],
- replace: true,
- scope: {
- ngModel: '=',
- play: '&'
- },
- templateUrl: '/views/nprListItem.html',
- link: function(scope, ele, attr) {
- scopescope.duration = scope.ngModel.audio[0].duration.$text;
- }
- }
- });
我們不會逐個解釋每個選項的意義,因為我們有一篇專門的深入文章來介紹它們(http://www.ng-newsletter.com /posts/directives.html)。這里我們只需要明白,現(xiàn)在我們就能在HTML里使用這個自定義的指令屬性了,它會將它所在的DOM元素 替換為我們給定的templateUrl所指向的模版里的內(nèi)容(在 /views/nprListItem 中)。
現(xiàn)在,我們的主HTML文件可以保持整潔,而將用來渲染列表內(nèi)容的view,創(chuàng)建在這個單獨提取出來的模版文件里:
- <div class="nprLink row" ng-click="play(ngModel)">
- <span class="name large-8 columns">
- <button class="large-2 small-2 playButton columns"><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>
注意我們在模版文件里用ngModel來指向之前的program數(shù)據(jù),因為在創(chuàng)建自定義指令屬性時,我們做了設(shè)置。
現(xiàn)在,我們在主HTML文件里就不用再寫上面那么多HTML,而只要簡單地?fù)Q上我們的自定義指令屬性npr-link:
- <ul id="programs_list" class="">
- <li ng-repeat="program in programs">
- <span npr-link play='play(program)' ng-model="program"></span>
- </li>
- </ul>
在下一章中,我們會介紹Service的用法,以及如何在我們應(yīng)用的多個controller之間通訊。
本系列的官方代碼庫可從github上下載:
https://github.com/auser/ng-newsletter-beginner-series.
要將這個代碼庫保存到本地,請先確保安裝了git,clone此代碼庫,然后check out其中的part5分支。我們使用XHR獲取模版,所以你需要在本地服務(wù)器上運行這一章的代碼。在part5分支里我們提供了服務(wù)器端代碼:
- git clone https://github.com/auser/ng-newsletter-beginner-series.git git checkout -b part5
- ./bin/server.sh