這篇文章主要介紹了簡介可以自動完成UI的AngularJS工具angular-smarty,包括其中隔離範圍綁定指令符和promise的使用,需要的朋友可以參考下
我們最近為我們的論壇增加了一個自動完成功能(稱為Smarty),在要求專業人員簡介的首頁上。這是一個超有用的功能,因為它有助於我們將使用者導航到他們真正想去的地方。它很有意思,也是用AngularJS構建的!
我們希望Smarty能夠:
通過使用者的給定輸入 (一個首碼),通過一個HTTP請求後自動提供建議
顯示一個建議的下拉式清單
當使用者輸入時更新
足夠快,能夠跟得上使用者的輸入
導航直觀且能夠關閉
可重用
以往沒有AngularJS的經驗,這個項目是我使用這個架構的入門項目。它真的成為了一次寶貴的學習經驗,而且我發現這個架構許多方面相互借鑒地很好且能很好地符合我上面列出的要求。當然,還有一些問題尚在學習過程中!
AngularJS的樂趣所在
我最喜歡Angular的一點是它是如何分解成具有明確目的的概念的。Smarty廣泛使用了其中的兩個概念——Directives 和 Services。Directive和DOM綁定在了一起,用來管理和元素之間的互動;而Service通過依賴注入的方式為Controller和Directive提供了獨立的可以重用的邏輯。
Angular提供了很多內建的Directive和Service,我們在這個項目中使用到了其中的多個。
為了顯示建議(見要求2),我們使用了ngIf 和 ngRepeat 指令來有條件的顯示和填充建議下來列表。
為了實現當使用者輸入的時候更建立議內容(見要求3),我們使用了ngModel 指令將$scope上首碼變數上輸入元素的請求和Scope上的$watch方法雙向繫結起來,用來監聽首碼的變化。
我們需要考慮的一件事是這個自動完成功能的更新速度可能跟不上使用者的輸入速度(見要求4)。因為每當首碼的值發生變化時,Smarty都要發送一個HTTP請求(通過內建的$http Service)。我們決定用 $http 的可選配置參數($http.get(requestUrl, {cache: true}))來緩衝結果。這是提升效能的一個很簡單的方法。
我們還寫了自訂的Directive和Service來滿足我們的特定需求:
smartySuggestor Service:smartySuggestor 提供了一個getSmartySuggestions()函數,用來接受使用者產生的首碼作為它的參數,並通過http請求訪問我們後端的suggestor服務來得到自動完成的建議。(見要求1)。
smartyInput Directive: 我們所面臨的一個挑戰是,定義出一個使用者和下拉式清單之間的所有可能的互動,並且寫測試案例,以確保在開發期間和開發之後,這些功能都是完好的。我們使用一個Directive(smartyInput)來包含使用者和下拉式清單之間所有可能發生的互動(見要求6),同時,我們使用內建的ng-mouseover 和 ng-click 指令(Directive)來定義下拉式清單和滑鼠事件之間的互動。
正如我前邊所提到的,我們所面臨的一個挑戰是我們要確保我們沒有破壞掉所有使用者和下拉式清單之間可能發生的互動方式。為了追蹤這些使用者互動,並確保我們沒有在開發的過程中破壞掉它,我們使用了Jasmine測試架構。Jasmine結合這angular-mocks一塊兒使用,可以方便我們為smarty寫包含描述的測試案例,像我們可以為下拉式清單寫“點擊外部應該會消失”("should disappear on outside click"),可以為請求表單的輸入寫“按斷行符號時應該填充上合適的值” (“should, on enter, fill with the appropriate value”)。
學習總結
儘管簡潔實用的AngularJS架構現在對我來講,是可以輕鬆讀懂的,但是確實要花些時間來學習。隔離範圍綁定指令符和promise是我學習過程中特別棘手的兩項專題。當我研究隔離範圍時,我發現自己十分希望能看到更多的關於人們如何在他們的項目中使用不同綁定的例子,所以下面我為每種綁定類型給出一個例子。
=:本地和父範圍之間的雙向資料繫結
控制器:
?
1$scope.selected = -1;
HTML:
?
1
SmartyInput指令符:
?
1scope: {index: "=", ...}
SmartyInput雙向繫結指令符將位於控制器範圍的selected綁定到指令範圍的index索引上面,這樣可以使得在index索引(建議列表中當前選定的索引)中的變化結果與指令(例如使用者按向上/向下箭頭)的相互作用會傳播到控制器。
&: 本地與父範圍之間的單向資料繫結
控制器:
?
1$scope.setSelected = function(newValue) {...};
HTML:
?
1
SmartyInput指令符:
?
1scope: {select: "&", ... }...scope.select({"x": parseInt(scope.index) + 1});
SmartyInput指令符將控制器範圍中的setSelected()函數綁定到指令範圍中的select()函數上,這樣可以使指令符在不改變setSelected()函數的情況下使用它。
@:將計算運算式綁定到本地範圍
控制器:
?
1$scope.prefix = ""
HTML:
?
1
SmartySuggestions指令符:
?
1scope: {prefix: "@", ...}
通過SmartySuggestions指令符,可在建議菜單中顯示prefix的值,所以此處使用計算運算式{{prefix}}。這種綁定方式在多項複雜運算式中更常用,比如:next-index=“{{selected + 1}}”。
Promises
promise是用於執行非同步任務的技術。一個promise具有一個名為then()的方法,該方法在promise執行時會作為被執行的的回呼函數的一個參數。當非同步任務完成,promise會將一段訊息傳給那個回呼函數。當使用者輸入的prefix首碼中檢測到更改,則promise就會出現在Smarty的代碼中,然後我們會用$http執行一個GET請求,用來更新顯示給使用者建議的列表。
這個過程看起來是這樣的:
當$scope.$watch在$scope.prefix(請注意,此處綁定到使用者UI輸入介面)的值中註冊了一個更改,此時會調用getSmartySuggesction()。
?
1
2var promise = smartySuggestor.getSmartySuggestions($scope.prefix);promise.then(function(data) {
$scope.suggestions = data;});
在getSmartySuggesctions()中,$http.get會返回一個參與伺服器響應的promise。
?
1
2
3
4
5
6
7
8
9
10
11
12
13function getSmartySuggestions(prefix) {
requestParams["query"] = escape(prefix.toLowerCase());
var promise = $http.get(requestUrl(),
{
params: requestParams,
cache: true
}
).then(function(response) {
return response.data.slice(0, 5).map(function(item) {
return item.Name;
});
});
return promise;}
伺服器響應看起來是這樣的:
?
1[{"ID":13,"Name":"Portrait Photography"},{"ID":24,"Name":"Commercial Photography"},{"ID":21,"Name":"Pet Photography"},{"ID":16,"Name":"Event Photography"},{"ID":26,"Name":"Headshot Photography"}]
接下來,我們會調用存在於promise中的then()方法,在解析伺服器響應回調中進行傳遞。then()方法返回一個新的promise,它處理瞭解析過的響應,並儲存到通過getSmartySuggestions()返回的promise中。
解析過的響應看起來是這樣的:[“Portrait Photography”, “Commercial Photography”, “Pet Photography”, “Event Photography”, “Headshot Photography”]。
最終,回到$scope.$watch,我們調用promise中的then()方法,為變數suggestions分配這些解析過的響應。
?
1
2
3
4var promise = smartySuggestor.getSmartySuggestions($scope.prefix);
promise.then(function(data) {
$scope.suggestions = data;
});