AngularJS 指令Directives實踐指南(四)

Transclusion(嵌入)

Transclusion是讓我們的指令包含任意內容的方法。我們可以延時提取並在正確的scope下編譯這些嵌入的內容,最終將它們放入指令模板中指定的位置。 如果你在指令定義中設置 transclude:true,一個新的嵌入的scope會被創建,它原型繼承子父scope。 如果你想要你的指令使用隔離的scope,但是它所包含的內容能夠在父scope中執行,transclusion也可以幫忙。

假設我們註冊一個如下的指令:

app.directive('outputText'function() {
return {
transclude: true,
scope: {},
template: '<div ng-transclude></div>'
};
});

它使用如下:

<div output-text>
<p>Hello `name`</p>
</div>

ng-transclude 指明在哪裏放置被嵌入的內容。在這個例子中DOM內容 <p>Hello `name`</p> 被提取和放置到 <div ng-transclude></div> 內部。有一個很重要的點需要注意的是,表達式`name`所對應的屬性是在父scope中被定義的,而非子scope。你可以在這個Plunker例子中做一些實驗。如果你想要學習更多關於scope的知識,可以閱讀這篇文章

transclude:’element’ 和 transclude:true的區別

有時候我我們要嵌入指令元素本身,而不僅僅是它的內容。在這種情況下,我們需要使用 transclude:’element’。它和 transclude:true 不同,它將標記了 ng-transclude 指令的元素一起包含到了指令模板中。使用transclusion,你的link函數會獲得一個名叫 transclude 的鏈接函數,這個函數綁定了正確的指令scope,並且傳入了另一個擁有被嵌入DOM元素拷貝的函數。你可以在這個 transclude 函數中執行比如修改元素拷貝或者將它添加到DOM上等操作。 類似 ng-repeat 這樣的指令使用這種方式來重複DOM元素。仔細研究一下這個Plunker,它使用這種方式複製了DOM元素,並且改變了第二個實例的背景色。

同樣需要注意的是,在使用 transclude:’element’的時候,指令所在的元素會被轉換成HTML註釋。所以,如果你結合使用 transclude:’element’ 和 replace:false,那麼指令模板本質上是被添加到了註釋的innerHTML中——也就是說其實什麼都沒有發生!相反,如果你選擇使用 replace:true,指令模板會替換HTML註釋,那麼一切就會如果所願的工作。使用 replade:false 和 transclue:’element’有時候也是有用的,比如當你需要重複DOM元素但是並不想保留第一個元素實例(它會被轉換成註釋)的情況下。對這塊還有疑惑的同學可以閱讀stackoverflow上的這篇討論,介紹的比較清晰。

controller 函數和 require

如果你想要允許其他的指令和你的指令發生交互時,你需要使用 controller 函數。比如有些情況下,你需要通過組合兩個指令來實現一個UI組件。那麼你可以通過如下的方式來給指令添加一個 controller 函數。

app.directive('outerDirective'function() {
return {
scope: {},
restrict: 'AE',
controller: function($scope, $compile, $http) {
// $scope is the appropriate scope for the directive
this.addChild = function(nestedDirective) { // this refers to the controller
console.log('Got the message from nested directive:' + nestedDirective.message);
};
}
};
});

這個代碼爲指令添加了一個名叫 outerDirective 的controller。當另一個指令想要交互時,它需要聲明它對你的指令 controller 實例的引用(require)。可以通過如下的方式實現:

app.directive('innerDirective'function() {
return {
scope: {},
restrict: 'AE',
require: '^outerDirective',
link: function(scope, elem, attrs, controllerInstance) {
//the fourth argument is the controller instance you require
scope.message = "Hi, Parent directive";
controllerInstance.addChild(scope);
}
};
});

相應的HTML代碼如下:

<outer-directive>
<inner-directive></inner-directive>
</outer-directive>

require: ‘^outerDirective’ 告訴Angular在元素以及它的父元素中搜索controller。這樣被找到的 controller 實例會作爲第四個參數被傳入到 link 函數中。在我們的例子中,我們將嵌入的指令的scope發送給父親指令。如果你想嘗試這個代碼的話,請在開啓瀏覽器控制檯的情況下打開這個Plunker。同時,這篇Angular官方文檔上的最後部分給了一個非常好的關於指令交互的例子,是非常值得一讀的。

一個記事本應用

這一部分,我們使用Angular指令創建一個簡單的記事本應用。我們會使用HTML5的 localStorage 來存儲筆記。最終的產品在這裏,你可以先睹爲快。
我們會創建一個展現記事本的指令。用戶可以查看他/她創建過的筆記記錄。當他點擊 add new 按鈕的時候,記事本會進入可編輯狀態,並且允許創建新的筆記。當點擊 back 按鈕的時候,新的筆記會被自動保存。筆記的保存使用了一個名叫 noteFactory 的工廠類,它使用了 localStorage。工廠類中的代碼是非常直接和可理解的。所以我們就集中討論指令的代碼。

第一步

我們從註冊 notepad 指令開始。

app.directive('notepad'function(notesFactory) {
return {
restrict: 'AE',
scope: {},
link: function(scope, elem, attrs) {
},
templateUrl: 'templateurl.html'
};
});

這裏有幾點需要注意的:

  • 因爲我們想讓指令可重用,所以選擇使用隔離的scope。這個指令可以擁有很多與外界沒有關聯的屬性和方法。

  • 這個指令可以以屬性或者元素的方式被使用,這個被定義在 restrict 屬性中。

  • 現在的link函數是空的

  • 這個指令從 templateurl.html 中獲取指令模板

第二步

下面的HTML組成了指令的模板

<div class="note-area" ng-show="!editMode">
<ul>
<li ng-repeat="note in notes|orderBy:'id'">
<a href="#" ng-click="openEditor(note.id)">`note`.`title`</a>
</li>
</ul>
</div>
<div id="editor" ng-show="editMode" class="note-area" contenteditable="true" ng-bind="noteText"></div>
<span><a href="#" ng-click="save()" ng-show="editMode">Back</a></span>
<span><a href="#" ng-click="openEditor()" ng-show="!editMode">Add Note</a></span>

幾個重要的注意點:

  • note 對象中封裝了 title,id 和 content。

  • ng-repeat 用來遍歷 notes 中所有的筆記,並且按照自動生成的 id 屬性進行升序排序。

  • 我們使用一個叫 editMode 的屬性來指明我們現在在哪種模式下。在編輯模式下,這個屬性的值爲 true 並且可編輯的 div 節點會顯示。用戶在這裏輸入自己的筆記。

  • 如果 editMode 爲 false,我們就在查看模式,顯示所有的 notes。

  • 兩個按鈕也是基於 editMode 的值而顯示和隱藏。

  • ng-click 指令用來響應按鈕的點擊事件。這些方法將和 editMode 一起添加到scope中。

  • 可編輯的 div 框與 noteText 相綁定,存放了用戶輸入的文本。如果你想編輯一個已存在的筆記,那麼這個模型會用它的文本內容初始化這個 div 框。

第三步

我們在scope中創建一個名叫 restore() 的新函數,它用來初始化我們應用中的各種控制器。 它會在 link 函數執行的時候被調用,也會在 save 按鈕被點擊的時候調用。

scope.restore = function() {
scope.editMode = false;
scope.index = -1;
scope.noteText = '';
};

我們在 link 函數的內部創建這個函數。 editMode 和 noteText 之前已經解釋過了。 index 用來跟蹤當前正在編輯的筆記。 當我們在創建一個新的筆記的時候,index 的值會設成 -1. 我們在編輯一個已存在的筆記的時候,它包含了那個 note 對象的 id 值。

第四步

現在我們要創建兩個scope函數來處理編輯和保存操作。

scope.openEditor = function(index) {
scope.editMode = true;
 
if (index !== undefined) {
scope.noteText = notesFactory.get(index).content;
scope.index = index;
else {
scope.noteText = undefined;
}
};
 
scope.save = function() {
if (scope.noteText !== '') {
var note = {};
 
note.title = scope.noteText.length > 10 ? scope.noteText.substring(0, 10) + '. . .' : scope.noteText;
note.content = scope.noteText;
note.id = scope.index != -1 ? scope.index : localStorage.length;
scope.notes = notesFactory.put(note);
}
 
scope.restore();
};

這兩個函數有幾點需要注意:

  • openEditor 爲編輯器做準備工作。如果我們在編輯一個筆記,它會獲取當前筆記的內容並且通過使用 ng-bind 將內容更新到可編輯的 div 中。

  • 如果我們在創建一個新的筆記,我們會將 noteText 設置成 undefined,以便當我們在保存筆記的時候,觸發相應的監聽器。

  • 如果 index 參數是 undefined,它表明用戶正在創建一個新的筆記。

  • save 函數通過使用 notesFactory 來存儲筆記。在保存完成後,它會刷新 notes 數組,從而監聽器能夠監測到筆記列表的變化,來及時更新。

  • save 函數調用在重置 controllers 之後調用restore(),從而可以從編輯模式進入查看模式。

第五步

在 link 函數執行時,我們初始化 notes 數組,並且爲可編輯的 div 框綁定一個 keydown 事件,從而保證我們的 nodeText 模型與 div 中的內容保持同步。我們使用這個 noteText 來保存我們的筆記內容。

var editor = elem.find('#editor');
 
scope.restore(); // initialize our app controls
scope.notes = notesFactory.getAll(); // load notes
 
editor.bind('keyup keydown'function() {
scope.noteText = editor.text().trim();
});

第六步

最後,我們在HTML如同使用其他的HTML元素一樣使用我們的指令,然後開始做筆記吧。

<h1 class="title">The Note Making App</h1>
<notepad/>

總結

一個很重要的點需要注意的是,任何使用jQuery能做的事情,我們都能用Angular指令來做到,並且使用更少的代碼。所以,在使用jQuery之前,請考慮一下我們能否在不進行DOM操作的情況下以更好的方式來完成任務。試着使用Angular來最小化jQuery的使用吧。
再來看一下我們的筆記本應用,刪除筆記的功能被故意漏掉了。鼓勵讀者們自己實驗和實現這個功能。 你可以從GitHub上下到這個Demo的源代碼。

原文鏈接: sitepoint 翻譯: 伯樂在線 陳 鑫偉
譯文鏈接: http://blog.jobbole.com/62999/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章