AngularJS 自定義 Directive 及程式碼示例
前面一篇介紹了各種常用的AngularJS內建的Directives以及對應的程式碼例項。這篇我們再看看如何建立自己的Directive吧!
什麼時候需要自定義Directive?
1. 使你的Html更具語義化,不需要深入研究程式碼和邏輯即可知道頁面的大致邏輯。
2. 抽象一個自定義元件,在其他地方進行重用。
看一下如下2個程式碼片段:
示例1:
<body> <div> <p>This is your class name.</p> <div> <p>Your teacher:</p> <p>Mr. Wang</p> <p>35 years old</p> <p>English</p> <p>Descriptions: 1.85cm tall, with a pair of brown glasses, unmarried, easy going etc.</p> </div> <div> <div> <p>Students in the class:</p> <div> <p>Jack</p> <p>Male</p> <p>15</p> <p>Description: Smart ...</p> </div> <div> <p>May</p> <p>Female</p> <p>14</p> <p>Description: Diligent ...</p> </div> <div> <p>Tom</p> <p>Male</p> <p>15</p> <p>Description: Naughty ...</p> </div> <div> <p>Alice</p> <p>Female</p> <p>14</p> <p>Description: Smart ...</p> </div> </div> </div> </div> </body>
示例2:
1 <body ng-app> 2 <class-info> 3 <teacher-info></teacher-info> 4 <student-infos></student-infos> 5 </class-info> 6 </body>
示例1中的程式碼你可能要完整的看完才能知道邏輯(當然示例1也不復雜,你可以想象下真實的場景要比這個複雜的多的多),不是說示例2中的程式碼少(邏輯被轉移到其他地方去了),而是在示例2中,光看Html標籤就知道這個頁面是在展示班級資訊,班級資訊中還有班主任的資訊和所有學生的資訊。
另外,示例1中,若一個班級的學生有30個,學生資訊的Html會出現30次,如果將來發生變動,這30出學生資訊的程式碼都需要改動。
製作一個屬於自己的Directive
示例3:
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('ngCustomDirectiveTest', []); app.controller('myController', ['$scope', function ($scope) { $scope.info = { yourname: 'Jack', template: 'template.html' }; }]); // 自定義Element的Directive app.directive("studentInfo", function () { return { // A 代表 Attribute // C 代表 Class // E 代表 Element // ACE 表示同時建立 A、C、E 三種 restrict: 'ACE', // templateUrl 指向獨立的Html檔案,AngularJS會用Html檔案中的內容替換studentInfo物件 templateUrl: 'template.html' }; }); })(); </script> </head> <body ng-app="ngCustomDirectiveTest"> <div ng-controller="myController as myCtrl"> <student-info></student-info> <br /> <data-student-info></data-student-info> <br /> <div student-info></div> <br /> <div data_student-info></div> <br /> <div class="student-info"></div> <br /> <div class="data-student-info"></div> <br /> </div> </body> </html>
template.html:
1 <div> 2 <p>This is a custom template.</p> 3 <p>Your name: {{info.yourname}}</p> 4 </div>
注意:你可能還見過restrict:’M',或者Directive的命名以pre_suf、pre:suf這樣的程式碼書寫方式,這些都已經“過時”了,最潮的restrict僅使用ACE三種,命名方式使用pre-suf。
另外,你可能疑惑,為什麼加上”data-”字首的為什麼也能被解析?實際上AngularJS在處理Directive時,首先會忽略Directive命名中的”data-”或者”x-”字首,因此無論你加上”data-”還是”x-”,AngularJS還是能正確解析的,不過”x-”也是一種過時的寫法,我們可以忽略。
好了,是不是很容易?屬於我們自己的Directive就這樣建立成功了,接著讓我們更深入一些,看一下Directive的scope屬性。首先看一下以下3段程式碼:
示例4(student-info直接使用了包含它的Controller的Scope中的變數jack和alice):
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('ngCustomDirectiveTest', []); app.controller('myController', ['$scope', function ($scope) { $scope.jack = { name: 'Jack', sex: 'Male' }, $scope.alice = { name: 'Alice', sex: 'Female' } }]); app.directive("studentInfo", function () { return { restrict: 'E', template: '<div><p>Student name: {{jack.name}}</p><p>Student sex: {{jack.sex}}</p></div><br /><div><p>Student name: {{alice.name}}</p><p>Student sex: {{alice.sex}}</p></div>' }; }); })(); </script> </head> <body ng-app="ngCustomDirectiveTest"> <div ng-controller="myController as myCtrl"> <student-info></student-info> </div> </body> </html>
示例5(和示例1類似,直接使用包含student-info的Controller中的變數students,在template中使用ng-repeat展示學生資訊):
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('ngCustomDirectiveTest', []); app.controller('myController', ['$scope', function ($scope) { $scope.students = [ { name: 'Jack', sex: 'Male' }, { name: 'Alice', sex: 'Female' } ]; }]); app.directive("studentInfo", function () { return { restrict: 'E', template: '<div ng-repeat="stu in students"><p>Student name:{{stu.name}}</p><p>Student sex:{{stu.sex}}</p></div>' }; }); })(); </script> </head> <body ng-app="ngCustomDirectiveTest"> <div ng-controller="myController as myCtrl"> <student-info></student-info> </div> </body> </html>
示例6(定義兩個不同的Controller:jackController和aliceController,使student-info處於2個不同的controller中):
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('ngCustomDirectiveTest', []); app.controller('jackController', ['$scope', function ($scope) { $scope.student = { name: 'Jack', sex: 'Male' } }]); app.controller('aliceController', ['$scope', function ($scope) { $scope.student = { name: 'Alice', sex: 'Female' } }]); app.directive("studentInfo", function () { return { restrict: 'E', template: '<div><p>Student name:{{student.name}}</p><p>Student sex:{{student.sex}}</p></div>' }; }); })(); </script> </head> <body ng-app="ngCustomDirectiveTest"> <div ng-controller="jackController as jackCtrl"> <student-info></student-info> </div> <br /> <div ng-controller="aliceController as aliceCtrl"> <student-info></student-info> </div> </body> </html>
上述三種方式,都能達到我們所需的目的:自定義一個名為student-info的Directive,展示Controller中的學生資訊。但仔細分析上述3種不同的程式碼,能發現它們各自有不同的問題:
1. 示例4中,student-info的template中的所有表示式嚴重依賴Controller中的變數定義,導致student-info無法抽象成一個公共的學生資訊展示模組。
2. 示例5中,雖然使用ng-repeat封裝了程式碼,但是還是存在依賴Controller中students變數的問題,示例5僅比示例4稍微好點。
3. 示例6中,定義了不同的Controller來隔離作用域,但N個學生需要定義N個作用域,並且定義Controller時,還是必須定義一個名為student的變數,否則程式碼無法正確執行,因此還是存在耦合性。
好吧,讓我們看看AngularJS為我們提供的優雅的解決方案-Isolate scope:
示例7(通過使用=attr將Isolate scope中的屬性賦值給Directive的名為’attr’的Attribute):
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('ngCustomDirectiveTest', []); app.controller('myController', ['$scope', function ($scope) { $scope.jack = { name: 'Jack', sex: 'Male' }, $scope.alice = { name: 'Alice', sex: 'Female' } }]); app.directive("studentInfo", function () { return { restrict: 'E', // 定義student-info的Isolate scope scope: { // 作用域內定義一個變數:newNameInScope // 值對應到Directive中的info屬性 newNameInScope: '=info' }, // template 不再依賴外部, 僅依賴內部的newNameInScope變數 template: '<div><p>Student name: {{newNameInScope.name}}</p><p>Student sex: {{newNameInScope.sex}}</p></div>' }; }); })(); </script> </head> <body ng-app="ngCustomDirectiveTest"> <div ng-controller="myController as myCtrl"> <!--將myController中的jack屬性傳遞給info--> <student-info info="jack"></student-info> <br /> <!--將myController中的alice屬性傳遞給info--> <student-info info="alice"></student-info> </div> </body> </html>
不同之處已經在註釋中說明,示例7已經完全將student-info與外界隔離,不在存在耦合性,真正達到了我們自定義Directive的目的2(見本文”什麼時候需要自定義Directive”部分)。
讓我們再對示例7進行一些調整:
示例8:
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('ngCustomDirectiveTest', []); app.controller('myController', ['$scope', function ($scope) { $scope.jack = { name: 'Jack', sex: 'Male' }, $scope.alice = { name: 'Alice', sex: 'Female' } }]); app.directive("studentInfo", function () { return { restrict: 'E', scope: { newNameInScope: '=info' }, // 這裡的alice將不能獲取Controller中的變數alice的資訊 template: '<div><p>Student name: {{newNameInScope.name}}</p><p>Student sex: {{newNameInScope.sex}}</p><br /><p>Deskmate name: {{alice.name}}</p><p>Deskmate sex: {{alice.sex}}</p></div>' }; }); })(); </script> </head> <body ng-app="ngCustomDirectiveTest"> <div ng-controller="myController as myCtrl"> <student-info info="jack"></student-info> </div> </body> </html>
這個就是所謂的封閉(Isolate),對比一下示例4,當建立student-info時指定了scope屬性後,不在scope中指定的變數,在student-info中將無法被識別,做到了“封閉”。這樣,當你定義一個公共模組時,不會因為在不同的Controller中使用而產生意想不到的問題。因此當你需要定義一個具有隔離性的Directive時,即使不需要傳遞Controller中的變數,也務必加上scope屬性。
不過我們只能將一個字串或者一個物件傳入Isolate scope中,試想若遇到某些特殊情況,需要直接包含指定的Html片段時怎麼辦?AngularJS也是有這樣的功能的。
示例9:
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('ngCustomDirectiveTest', []); app.controller('myController', ['$scope', function ($scope) { $scope.jack = { name: 'Jack', sex: 'Male' }, $scope.alice = { name: 'Alice', sex: 'Female' } }]); app.directive("studentInfo", function () { return { restrict: 'E', // 指定transclude屬性為true transclude: true }; }); })(); </script> </head> <body ng-app="ngCustomDirectiveTest"> <div ng-controller="myController as myCtrl"> <!--指明student-info將會使用transclude模式--> <student-info ng-transclude> <!-- student-info的內容由使用者自己指定,並且內容中能訪問student-info的scope以外的變數 --> <p>Student name: {{jack.name}}</p> <p>Student sex: {{jack.sex}}</p> <br /> <p>Deskmate name: {{alice.name}}</p> <p>Deskmate sex: {{alice.sex}} </student-info> </div> </body> </html>
其他自定義Directive的示例
示例10(自定義Directive操作DOM,官方文件中的demo):
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('docsTimeDirective', []); app.controller('Controller', ['$scope', function ($scope) { $scope.format = 'M/d/yy h:mm:ss a'; }]) app.directive('myCurrentTime', ['$interval', 'dateFilter', function ($interval, dateFilter) { function link(scope, element, attrs) { var format, timeoutId; function updateTime() { element.text(dateFilter(new Date(), format)); } scope.$watch(attrs.myCurrentTime, function (value) { format = value; updateTime(); }); element.on('$destroy', function () { $interval.cancel(timeoutId); }); timeoutId = $interval(function () { updateTime(); }, 1000); } return { link: link }; }]); })(); </script> </head> <body ng-app="docsTimeDirective"> <div ng-controller="Controller"> Date format: <input ng-model="format"> <hr /> Current time is: <span my-current-time="format"></span> </div> </body> </html>
如果想要使Directive改變DOM,一般會用到link引數,其原型為:function link(scope, element, attrs) {…}:
- scope: 與當前元素結合的scope
- elment:當前元素
- $attrs:當前元素的屬性物件
示例11(通過使用&attr開放Directive,將自定義的方法繫結到Directive上):
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('isoFnBindTest', []); app.controller('myController', ['$scope', function ($scope) { $scope.name = ''; $scope.message = ''; $scope.isHide = true; $scope.sayHello = function (message, name) { $scope.isHide = false; $scope.name = name; $scope.message = message; alert($scope.message + ',' + $scope.name); }; }]); app.directive('myGreeting', function () { return { restrict: 'E', transclude: true, scope: { // Step 2: greet方法繫結到onGreet屬性(對應Html中的on-greet),並將greet的輸入引數傳給onGreet 'greet': '&onGreet' }, templateUrl: 'my-greeting.html' }; }); })(); </script> </head> <body ng-app="isoFnBindTest"> <div ng-controller="myController"> <!-- Step 3: on-greet指向了myController中的sayHello方法,此時on-greet中能直接訪問到greet的輸入引數--> <my-greeting on-greet="sayHello(message, name)"> <div ng-hide="isHide"> {{message}}, {{name}}! </div> </my-greeting> </div> </body> </html>
my-greeting.html:
1 <div> 2 <!-- Step1: 一旦觸發click, 將呼叫Isolate scope中的greet方法--> 3 <button ng-click="greet({message: 'Hello', name: 'Tom'})">Click me!</button> 4 <div ng-transclude></div> 5 </div>
示例12(Directive偵聽事件,官方Demo):
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('dragModule', []); app.directive('myDraggable', ['$document', function ($document) { return { link: function (scope, element, attr) { var startX = 0, startY = 0, x = 0, y = 0; element.css({ position: 'relative', border: '1px solid red', backgroundColor: 'lightgrey', cursor: 'pointer' }); element.on('mousedown', function (event) { // Prevent default dragging of selected content event.preventDefault(); startX = event.pageX - x; startY = event.pageY - y; $document.on('mousemove', mousemove); $document.on('mouseup', mouseup); }); function mousemove(event) { y = event.pageY - startY; x = event.pageX - startX; element.css({ top: y + 'px', left: x + 'px' }); } function mouseup() { $document.off('mousemove', mousemove); $document.off('mouseup', mouseup); } } }; }]); })(); </script> </head> <body ng-app="dragModule"> <span my-draggable>Drag ME</span> </body> </html>
示例13(Directive之間的相互作用,官方Demo):
<!DOCTYPE> <html> <head> <script src="/Scripts/angular.js"></script> <script type="text/javascript"> (function () { var app = angular.module('docsTabsExample', []); app.directive('myTabs', function () { return { restrict: 'E', transclude: true, scope: {}, controller: function ($scope) { var panes = $scope.panes = []; $scope.select = function (pane) { angular.forEach(panes, function (pane) { pane.selected = false; }); pane.selected = true; }; this.addPane = function (pane) { if (panes.length === 0) { $scope.select(pane); } panes.push(pane); }; }, templateUrl: 'my-tabs.html' }; }); app.directive('myPane', function () { return { // 指定必須有myTabs物件,若物件不存在則會報錯,見下面的圖1 require: '^myTabs', // ^ 表示將在父級的範圍內查詢該物件, 沒有 ^ 表示在Directive內查詢該物件, 若範圍指定錯誤無法找到myTabs,js則會報錯 restrict: 'E', transclude: true, scope: { title: '@' }, link: function (scope, element, attrs, tabsCtrl) { tabsCtrl.addPane(scope); }, templateUrl: 'my-pane.html' }; }); })(); </script> </head> <body ng-app="docsTabsExample"> <my-tabs> <my-pane title="Hello"> <h4>Hello</h4> <p>Lorem ipsum dolor sit amet</p> </my-pane> <my-pane title="World"> <h4>World</h4> <em>Mauris elementum elementum enim at suscipit.</em> <p><a href ng-click="i = i + 1">counter: {{i || 0}}</a></p> </my-pane> </my-tabs> </body> </html>
my-tabs.html:
<div class="tabbable"> <ul class="nav nav-tabs"> <li ng-repeat="pane in panes" ng-class="{active:pane.selected}"> <a href="" ng-click="select(pane)">{{pane.title}}</a> </li> </ul> <div class="tab-content" ng-transclude></div> </div>
my-pane.html:
<div class="tab-pane" ng-show="selected" ng-transclude> </div>
參考資料
AngularJS官方文件:https://docs.angularjs.org/guide/directive
CodeSchool快速入門視訊:http://campus.codeschool.com/courses/shaping-up-with-angular-js/intro
相關文章
- AngularJS中自定義有關一個表格的DirectiveAngularJS
- AngularJS自定義Directive中link和controller的區別AngularJSController
- 自定義Directive使用ngModel
- web前端vue:自定義指令directiveWeb前端Vue
- 深入解析Vue.directive 自定義指令Vue
- AngularJS - 自定義指令AngularJS
- AngularJS核心之DirectiveAngularJS
- 學習AngularJs:Directive指令用法AngularJS
- Angular 自定義結構型指令 structural directive 的使用AngularStruct
- Vuejs進階知識(二十四)【自定義Directive】VueJS
- AngularJS自定義表單控制元件AngularJS控制元件
- 走進AngularJs(五)自定義指令----(下)AngularJS
- 走進AngularJs(三)自定義指令-----(上)AngularJS
- 走進AngularJs(四)自定義指令----(中)AngularJS
- SpringBoot?整合mongoDB並自定義連線池的示例程式碼Spring BootMongoDB
- vue自定義指令(Directive中的clickoutside.js)的理解VueIDEJS
- 一個MapReduce 程式示例 細節決定成敗(七) :自定義Key 及RecordReader
- Xcode自定義程式碼塊XCode
- AngularJS Directive 隔離 Scope 資料互動AngularJS
- AngularJS中Directive間互動實現合成AngularJS
- Opencv及常用方法示例程式碼OpenCV
- 自定義AndroidStudio程式碼模板Android
- AngularJS中寫一個包裹HTML元素的directiveAngularJSHTML
- Base64自定義編碼表及破解
- Android 程式設計程式碼-自定義 ToastAndroid程式設計AST
- uni-app中自定義動態底部tabbar(附示例原始碼)APPtabBar原始碼
- 自定義數字格式字串輸出示例字串
- KingbaseES 自定義運算子使用示例
- Log4Net配置詳解及輸出自定義訊息類示例
- xcode10 自定義程式碼塊XCode
- 自定義Android Studio程式碼模板Android
- rubymine設定自定義快捷程式碼片段
- jQuery自定義標籤程式碼例項jQuery
- javascript自定義右鍵選單程式碼JavaScript
- Android程式碼實現自定義ButtonAndroid
- [iOS] [OC] NSNotificationCenter 進階及自定義(附原始碼)iOS原始碼
- Vue 自定義元件directive使用總結Vue元件
- 安卓自定義註解支援和示例實現安卓