一、首先我們來了解下指令API
屬性 | 含義 |
restrict | 申明標識符在模版中作爲元素,屬性,類,註釋或組合,如何使用 |
priority | 設置模版中相對於其他標識符的執行順序 |
Template | 指定一個字符串式的內嵌模版,如果你指定了模版是一個URL,那麼是不會使用的 |
tempateUrl | 指定URL加載的模版,如果你已經指定了內嵌的模版字符串,那麼它不會使用的 |
Replace | 如果爲真,替換當前元素,如果是假或未指定,拼接到當前元素 |
Transclude | 移動一個標識符的原始字節帶你到一個新模版的位置 |
Scope | 爲這個標識符創建一個新的作用域,而不是繼承父作用域 |
Controller | 創建一個控制器通過標識符公開通信API |
Require | 當前標識符需要另外一個標識符提供正確的函數功能 |
Link | 通過代碼修改目標DOM元素的實例,添加事件監聽,建立數據綁定 |
Compile | 通過標識符拷貝編程修改DOM模版 |
在這裏,我們簡單的瞭解下每個屬性的含義及其簡單的作用,在後面我們將會通過一些勵志來解釋
接下來,我們來了解下指令定義對象
restrict:restrict屬性允許爲標識符指定聲明樣式,也就是說它可以作爲元素名,屬性,類或註釋。我們可以使用一個字符串來代表下表中的每個標誌,從而指定
一個或者多個聲明樣式
標誌 | 樣式 | 示例 |
E | Element | <my-menu title='products'></my-menu> |
A | Attribute | <div my-menu='products'></div> |
C | Class | <div class='my-menu:products'></div> |
M | Comment | <!--directive:my-menu products--> |
如果你希望標識符作爲元素後者屬性,你可以傳遞EA作爲restrict的字符串
如果省略了restrict屬性,默認就是A,詳細請看angular文檔
Priorities:爲應用程序指定順序,數值越大就越先運行,默認是爲零,一般情況下無需設置優先級
Templates:在創建組件時,angular允許你在模版中替換和包裝元素中的內容,如果你想創建如下的標籤視圖
不是使用一串<div><ul><li><a>等元素來實現,而是可以通過自定義創建標識符<tab-set><tab>,分別聲明每個頁籤的結構,可能如果下
<tab-set>
<tab title='Home'>
<p>Welcome home!</p>
</tab>
<tab title='Preferences'>
<!-- preferences UI goes here -->
</tab>
</tabset>
同時,你也可通過控制器爲title和頁籤內容進行數據綁定,並用這種方式做出菜單,手風琴,彈窗,對話框或其他應用需求
接下來,讓我們來看看temp拉特或者templateUrl屬性,指定替換DOM元素。在上表中,我們看到template可以用來設置模版內容的字符串,
templateUrl用於指定將被加載的服務器文件,正如接下來看到的示例,我們可以預緩存這些模版,一遍減少get請求數,提高性能
<html lang='en' ng-app='app'>
...
<body>
<hello></hello>
</body>
...
創建<hello></hello>標籤替換<div>hi there</div>,replace設置爲true允許拼接內容到元素上,設置replace成爲true
var appModule = angular.module('app', []);
appModule.directive('hello', function() {
return {
restrict: 'E',
template: '<div>Hi there</div>', replace: true
};
});
在加載到瀏覽器後,我們會看到hi there,通過查看頁面源碼,我們還是會看到<hello></hello>但是如果你檢查生成代碼(chrome,右擊hi htere,選擇檢查元素)你會看到
<body>
<div>Hi there</div>
</body>
<hello></hello>已經被模版中的<div>替換。
而相對於使用template 輸入html到字符串中不是很有意義,一般我們都會使用templateUrl,進行設置適當的頭部緩存
var appModule = angular.module('app', []);
appModule.directive('hello', function() {
return {
restrict: 'E',
templateUrl: 'helloTemplate.html', replace: true
};
});
在helloTemplate.html中,我們需要寫入
<div> Hi there</div>
如果你使用chrome瀏覽器,同源策略會組織可能會導致遇到一個錯誤“Origin null is not allowed by Access-Controll-Allow-Origin”.這裏你有兩種可選方式
1.通過服務器加載應用
2.chrome 中設置一個標誌,通過命令行‘chrome-allow-file-access-from-files’解決
然後通過templateUrl加載文件,會使用戶等待知道加載後看到標識符,如果你希望第一次頁面加載時就加載模版,你可以在script標籤中讓其作爲頁面的一部分,如下
<script type='text/ng-template' id='helloTemplateInline.html'>
<div>Hi there</div>
</script>
這裏的id屬性非常重要,因爲是URL鍵,angular用它來存儲模版,你應該在標識符的templateUrl中使用id來指定插入那個模版
還有我們可以通過$http或其他幾種機制加載模版,然後直接設置到angular所使用的$templateCache對象中,並可通過run函數調用它,使其在標識符運行之前讓這個模版在
緩存中可用
var appModule = angular.module('app', []);
appModule.run(function($templateCache) {
$templateCache.put('helloTemplateCached.html', '<div>Hi there</div>');
});
appModule.directive('hello', function() {
return {
restrict: 'E',
templateUrl: 'helloTemplateCached.html', replace: true
};
});
Transclusion(嵌入包含)
通過transclude屬性移動原始的內容到新模版中,當設置成爲true時,標識符會刪除原來的內容,並通過ng-transclude標識符使它重新插入到模版中
採用transclusion方式修改示例:
appModule.directive('hello', function() {
return {
template: '<div>Hi there <span ng-transclude></span></div>', transclude: true
};
});
應用在:
<div hello>Bob</div>
我們會看到‘Hi there Bob.’
編譯和鏈接函數
雖然插入模版是有用,但是在任何標識符真正有意義的工作發生在編譯活着鏈接功能裏
編譯和鏈接的功能就是angular爲引用創建實時視圖的後兩階段,讓我們來看下angular初始化過程的高層次視圖,按照次序
【1】腳本加載:加載angular,查找ng-app標識符找到應用綁定
【2】編譯階段:在這一階段,angular便利DOM標誌模版中所有註冊的標誌,對於每個標識符,基於標識符規則(template,replace,transclude等等)改造DOM,然後
如果編譯函數存在就調用它,結果一個編譯的template函數,它會調用所有的標識符蒐集的link韓素
【3】鏈接階段:爲了讓視圖動起來,angular爲每個標識符運行link函數,link函數通常在DOM或模型上創建監聽器,這些監聽器讓視圖和模型始終保持一致
因此到了編譯階段,它處理轉換了模版,鏈接階段,它處理了修改視圖中的數據,沿着這些思路,標識符中表一功能和鏈接功能主要區別就是鏈接功能轉換了模版自身,而連接功能在模型和視圖上創建了動態鏈接,就是在第二階段,作用域scpoes被附加到了編譯過程的link功能上,通過數據綁定,標識符變活了
二、作用域
獲取作用域scope的三種選擇
1.標識符DOM元素中已經存在的作用域
2,創建一個繼承封閉的控制器作用域的新作用域,以便讀取結構樹作用域的所有值。
3.獨立作用於,從父類中不繼承任何屬性,當你需要隔離這個標識符的操作和父類作用域時,創建可從用的組建來使用這個選項
我們可以用如下語法類創建這些作用域配置
已有作用域 | scope:false(如果沒有指定,這就是默認值) |
新作用域 | scope:true |
獨立作用域 | scope:{屬性名次和綁定風格} |
當你創建一個獨立作用域時,默認情況下不能訪問父類作用域的模型,但我們可以通過指定需要的屬性傳遞到標識符
注意:雖然獨立作用域並沒有繼承模型屬性,但他們仍然是父作用域的子節點,並$parent指向父類
我們可以通過標識符屬性的鍵值對父類傳遞指定的屬性給獨立作用域,這裏有三種可行的方式從父作用域傳遞數據,我們稱這些傳遞數據方式叫做“綁定策略”,你可以爲這個屬性名稱指定一個本地別名
沒有別名的語法如下:
scope: { attributeName1: 'BINDING_STRATEGY',
attributeName2: 'BINDING_STRATEGY', …
}
用別名的格式如下:
scope: { attributeAlias: 'BINDING_STRATEGY' + 'templateAttributeName',
…
}
符號 | 意義 |
@ | 傳遞字符串屬性,你可以通過使用改寫{{}}屬性值從粉筆作用域中進行數據綁定 |
= | 數據綁定屬性在標識符父作用域的屬性中 |
& | 傳遞一個來自父作用域的函數,稍後調用 |
在這我們用一個具體例子上的變化來說明它們,比如我們想創建一個expander標識符,展示一個標題欄,當點擊時擴展顯示額外內容
關閉的時候
打開的狀態
代碼如下:
<div ng-controller='SomeController'>
<expander class='expander' expander-title='title'>
{{text}}
</expander>
</div>
控制器代碼:
function SomeController($scope) {
$scope.title = 'Click me to expand';
$scope.text = 'Hi there folks, I am the content
+ 'that was hidden but is now shown.';
}
然後我們編寫標識符
angular.module('expanderModule', [])
.directive('expander', function(){
return {
restrict: 'EA', replace: true,
transclude: true,
scope: { title:'=expanderTitle' }, template: '<div>' +
'<div class="title" ng-click="toggle()">{{title}}</div>' +
'<div class="body" ng-show="showMe" ng-transclude></div>' +
'</div>',
link: function(scope, element, attrs) {
scope.showMe = false;
scope.toggle = function toggle() {
scope.showMe = !scope.showMe;
}
}
}
});
樣式:
.expander {
border: 1px solid black;
width: 250px;
}
.expander > .title {
background-color: black;
color: white; padding: .1em .3em; cursor: pointer;
}
.expander > .body {
padding: .1em .3em;
}
元素的功能
功能名 | |
Restrict:EA | 描述調用標識符爲元素或屬性,也就是<expander>..</expander>或 <div expander>...</div> |
Replace:true | 用提供的模版替換原來的元素 |
Transclude:true | 移動原始元素內容到提供的模版中的另外一個地方 |
Scope:{title:=expanderTitle}} | 創建一個叫title的本地作用域屬性,它是用來數據綁定到expander-title 屬性中聲明的parent-scope屬性,這裏,爲了方便expanderTitle衝命名 爲title,由於expanderTitle在模版中,我們飆血作用域 scope:{expanderTitle:'='}來引用它,但在這個場景中,其他標識符也 有一個title屬性,爲了防止引起歧義,我們將其重命名 |
Template:<div>+ | 爲標識符提供即將插入的模版,注意,我們使用ng-click和ng-show 來展示或隱藏自身,ng-transclude來申明原始的內容將何去何從, 同時注意嵌入的內容可以訪問父作用域,而不是封閉標識符的 作用域 |
Link: | 建立showMe模型,跟蹤expander的打開和關閉狀態,然後當用戶點擊title div時,調用定義的toggle函數 |
三、操作DOM元素
函數 | 描述 |
Controller(name) | 與控制器直接進行通信,這個函數返回綁定在元素上的控制器 如果這個元素不存在,它會遍歷DOM,然後查找最近的父控制 器代替,如果參數名字是可選的,用於指定同意元素上其他 標識符名稱,如果提供了,則返回標識符上的控制器,這個名字 應該使用駝峯式,也就是說用ngModel代替ng-model |
Injector() | 獲取當前元素或者父元素的注入器,允許在這些模塊中查找依賴 |
Scope() | 返回當前元素或者最近父元素的作用域 |
inHeritedData() | 和jquery的data()函數一樣,inheritedData()以封閉的方式設置以及獲取元素上的 數據,除了從當前元素獲取數據,它會便利DOM查找 |
下面例子,我們不用ng-show和ng-click重新實現之前expander示例
angular.module('expanderModule', [])
.directive('expander', function(){
return {
restrict: 'EA', replace: true, transclude: true,
scope: { title:'=expanderTitle' }, template: '<div>' +
'<div class="title">{{title}}</div>' +
'<div class="body closed" ng-transclude></div>' +
'</div>',
link: function(scope, element, attrs) {
var titleElement = angular.element(element.children().eq(0));
var bodyElement = angular.element(element.children().eq(1));
titleElement.bind('click', toggle);
function toggle() {
bodyElement.toggleClass('closed');
}
}
}
});
在上面我們從模版中移除了ng-click和ng-show標識符,然而,當用戶點擊expander標題時候,仍然執行預期的操作,我們從tittle元素上創建了一個jqLite元素,然後把toggle函數綁定到click事件上作爲它的回調,在toggle函數,我們在expander body元素上調用toggleClass()
來添加或移除closed的類,我們會設置這個元素class設置成的displat:none
.closed {
display: none;
}