AngularJS 教程:揭秘自定義指令
已發表: 2022-03-11隨著 JavaScript 作為一種全棧語言的快速發展,越來越多的應用程序正在使用框架,這些框架使 Web 瀏覽器能夠處理更多的 UI 處理,例如數據綁定、管理數據視圖、轉換數據和許多其他服務。 最有能力、可擴展和流行的框架之一是 AngularJS,AngularJS 框架中最有用的組件之一是一種叫做指令的東西。 AngularJS 提供了許多有用的指令,更重要的是,它為創建自定義指令提供了豐富的框架。
什麼是指令? 簡而言之,指令是操作並向 HTML DOM 元素添加行為的 JavaScript 函數。 指令可以非常簡單或極其複雜。 因此,牢牢掌握操縱它們的許多選項和功能至關重要。
在本教程中,將探索作為指令執行的四個函數並將其應用於 DOM,並提供示例。 這篇文章假設您熟悉 AngularJS 和自定義指令。 如果您是 Angular 新手,您可能會喜歡構建您的第一個 AngularJS 應用程序的教程。
AngularJS 指令生命週期的四個功能
有許多選項可以配置,這些選項如何相互關聯很重要。 在 AngularJS 編譯和鏈接 DOM 時,每個指令都會經歷類似於生命週期的過程。 指令生命週期在 AngularJS 引導過程中開始和結束,在頁面呈現之前。 在指令的生命週期中,如果定義了四個不同的函數可以執行。 每個都使開發人員能夠在生命週期的不同點控制和自定義指令。
這四個函數是: compile 、 controller 、 pre-link和post-Link 。
compile函數允許指令在編譯和鏈接之前操作 DOM,從而允許它添加/刪除/更改指令,以及添加/刪除/更改其他 DOM 元素。
控制器功能有助於指令通信。 兄弟姐妹和孩子指令可以請求他們的兄弟姐妹和父母的控制器傳達信息。
pre-link函數允許在 post-link 過程開始之前進行私有$scope操作。
post-link方法是該指令的主要工作方法。
在指令中,發生了編譯後的 DOM 操作,配置了事件處理程序,監視和其他東西也是如此。 在指令的聲明中,四個函數是這樣定義的。
.directive("directiveName",function () { return { controller: function() { // controller code here... }, compile: { // compile code here... return { pre: function() { // pre-link code here... }, post: function() { // post-link code here... } }; } } })
通常,並非所有功能都需要。 在大多數情況下,開發人員將按照以下模式簡單地創建一個控制器和後鏈接函數。
.directive("directiveName",function () { return { controller: function() { // controller code here... }, link: function() { // post-link code here... } } })
在此配置中,鏈接指的是鏈接後功能。
無論是定義了所有函數還是部分函數,它們的執行順序都很重要,尤其是它們相對於 AngularJS 應用程序的其餘部分的執行。
AngularJS指令函數執行相對於其他指令
考慮以下 HTML 片段,其中將指令parentDir 、 childDir和grandChildDir應用於 HTML 片段。
<div parentDir> <div childDir> <div grandChildDir> </div> </div> </div>
指令中函數的執行順序以及相對於其他指令的執行順序如下:
- 編譯階段
- 編譯函數:parentDir
- 編譯函數:childDir
- 編譯函數:grandChildDir
- 控制器和預鏈接階段
- 控制器功能:parentDir
- 預鏈接功能:parentDir
- 控制器功能:childDir
- 預鏈接功能:childDir
- 控制器功能:grandChildDir
- 預鏈接功能:grandChildDir
- 鏈接後階段
- 後鏈接功能:grandChildDir
- 後鏈接功能:childDir
- 後鏈接功能:parentDir
AngularJS 指令函數說明:Deep Dive
編譯階段首先發生。 本質上,編譯階段將事件偵聽器附加到 DOM 元素。 例如,如果特定的 DOM 元素綁定到$scope屬性,則允許使用$scope屬性的值對其進行更新的事件偵聽器將應用於 DOM 元素。 編譯過程從啟動 AngularJS 應用程序的根 DOM 元素開始,並使用深度優先遍歷向下遍歷 DOM 的分支,首先編譯父節點,然後編譯其子節點,一直到葉節點。
一旦編譯完成,就不能再從 DOM 中添加或刪除指令(儘管可以通過直接使用編譯服務來解決這個問題。下一階段是為所有指令調用控制器和預鏈接函數。當控制器被調用, $scope可用,可以使用。注入控制器的$element包含編譯後的模板,但不包含transcluded子內容(transcluded content是指令所在的HTML開始標籤和結束標籤之間的內容)根據定義,MVC 模式中的控制器只是將模型傳遞給視圖並定義處理事件的函數。因此,指令的控制器不應修改指令的 DOM,原因有兩個:它違反了控制器,並且被嵌入的子內容還沒有被添加到 DOM 中。那麼控制器除了修改$scope之外還能做什麼呢?控制器允許子指令與 wi 進行通信父指令。 控制器函數本身應該被認為是一個控制器對象,如果子指令請求它,它將被傳遞到子指令的 post-link 函數中。 因此,控制器通常用於通過創建具有可由其兄弟和子指令使用的屬性和方法的對象來促進指令通信。 父指令無法確定子指令是否需要其控制器,因此最好將此方法中的代碼限制為子指令可以安全使用的函數和屬性。

在控制器功能之後,執行預鏈接功能。 預鏈接功能對很多人來說是神秘的。 如果您閱讀 Internet 和書籍中的大部分文檔,人們會寫道,此功能僅在極少數情況下使用,人們幾乎永遠不需要它。 然後,這些相同的解釋未能給出可以使用它的情況的示例。
預鏈接功能真的一點都不復雜。 首先,如果您查看 AngularJS 源代碼,您會發現預鏈接功能的一個很好的示例:指令ng-init使用了它。 為什麼? 這只是執行涉及$scope的私有代碼的好方法; 不能被同級和子指令調用的代碼。 與控制器函數不同,預鏈接函數不會傳遞到指令中。 因此,它可用於執行修改其指令的$scope的代碼。 指令ng-init正是這樣做的。 當ng-init的 pre-link 函數執行時,它只是針對指令的$scope執行傳遞給指令的 JavaScript。 執行結果可通過$scope的原型繼承在其控制器、預鏈接和後鏈接函數執行期間對子指令進行,但無需訪問這些子指令以重新執行父預鏈接中的代碼鏈接功能。 此外,該指令可能需要執行與它希望保持私有的$scope無關的其他代碼。
一些有經驗的 AngularJS 開發人員會說這個私有代碼仍然可以放在控制器中,然後不被子指令調用。 如果該指令僅由編寫它的原始開發人員使用,但如果該指令將由其他開發人員分發和重用,則該論點將成立,那麼在預鏈接函數中封裝私有代碼可能非常有益。 由於開發人員永遠不知道隨著時間的推移他們的指令將如何被重用,保護私有代碼不被子指令執行是指令代碼封裝的好方法。 我認為將指令通信公共代碼放在控制器函數中,將私有代碼放在預鏈接函數中是一種很好的做法。 與控制器一樣,預鏈接不應該進行 DOM 操作,也不應該執行 transclude 函數,因為子指令的內容還沒有被鏈接。
對於每個指令,其控制器和預鏈接函數在其子指令的控制器和預鏈接函數之前執行。 一旦所有指令的控制器和鏈接前階段完成,AngularJS 就會開始鏈接階段,並為每個指令執行鏈接後功能。 鏈接階段與編譯、控制器和預鏈接執行流程相反,從葉子 DOM 節點開始,一直到根 DOM 節點。 鏈接後的 DOM 遍歷主要遵循深度優先路徑。 當每個子指令被鏈接時,它的鏈接後功能就會被執行。
post-link 函數是自定義 AngularJS 指令中最常實現的函數。 在這個函數中,幾乎可以做任何合理的事情。 可以操作 DOM(僅針對其自身和子元素),可以使用$scope ,可以使用父指令的控制器對象,可以運行 transclude 函數等。但是,有一些限制。 新指令不能添加到 DOM,因為它們不會被編譯。 此外,所有 DOM 操作都必須使用 DOM 函數完成。 只需在 DOM 元素上調用html函數並傳入新的 HTML 將刪除在編譯過程中添加的所有事件處理程序。 例如,這些將無法按預期工作:
element.html(element.html());
要么
element.html(element.html() + "<div>new content</div>");
該代碼不會導致 HTML 更改,但重新分配 DOM 元素的字符串版本將刪除在編譯過程中創建的所有事件處理程序。 通常,post-link 函數用於連接事件處理程序, $watch es 和$observe s。
一旦所有的鏈接後函數都執行完畢, $scope就會應用於編譯和鏈接的 DOM 結構,AngularJS 頁面就會活躍起來。
指令功能圖
下表列出了每個函數的用途、執行時可用的內容以及如何正確使用每個函數的最佳實踐。
執行 命令 | 指示 功能 | DOM | 嵌入 | $範圍 | 可調用 由孩子 |
---|---|---|---|---|---|
1 | 編譯 | DOM 尚未編譯,但模板已加載到 DOM 元素內容區域。 可以添加和刪除指令。 可以使用 DOM 函數和 HTML 字符串替換來操作 DOM。 | Transclude 函數可用但已棄用且不應調用。 | 無法使用。 | 子元素不能調用函數。 |
2 | 控制器 | 已編譯的 DOM 元素可用,但不應修改。 已嵌入的子內容尚未添加到 DOM 元素中。 不應發生 DOM 更改,因為這是一個控制器,並且尚未鏈接已嵌入的子內容。 | Transclude 函數可用但不應調用。 | $scope可用並且可以使用。 函數參數使用$injector服務注入。 | 函數被傳遞給鏈接函數的子指令,並且可以被它們調用。 |
3 | 預鏈接 | 已編譯的 DOM 元素可用但不應修改,因為尚未鏈接子指令 DOM 元素。 | Transclude 函數可用但不應調用。 | $scope可用並且可以修改。 | 子指令不能調用函數。 但可以調用父指令的控制器。 |
4 | 後鏈接 | 已編譯的 DOM 元素和子指令 DOM 元素可用。 DOM 只能用 DOM 函數修改(不能替換 HTML),並且只能添加不需要編譯的內容。 不允許添加/刪除指令。 | Transclude 函數可用並且可以調用。 | $scope可用並且可以使用。 | 不能被子指令調用,但可以調用父指令的控制器。 |
概括
在這篇關於 AngularJS 指令的教程中,我們了解了compile 、 controller 、 pre-link和post-link四個指令函數中的每一個的目的、執行順序和整體能力和用途。 在這四個函數中,controller 和 post-link 是最常用的,但對於需要更好地控制 DOM 或需要私有範圍執行環境的更複雜的指令,可以使用編譯和 pre-link 函數。