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/