天天看點

指令<AngularJs>

對于指令,可以把它簡單的了解成在特定DOM元素上運作的函數,指令可以擴充這個元素的功能。

首先來看個完整的參數示例再來詳細的介紹各個參數的作用及用法:

angular.module('myApp', []) 
.directive('myDirective', function() { 
    return { 
        restrict: String, 
        priority: Number, 
        terminal: Boolean, 
        template: String or Template Function: 
    function(tElement, tAttrs) {...}, 
    templateUrl: String, 
    replace: Boolean or String, 
    scope: Boolean or Object, 
    transclude: Boolean, 
    controller: String or 
    function(scope, element, attrs, transclude, otherInjectables) { ... }, 
    controllerAs: String, 
    require: String, 
    link: function(scope, iElement, iAttrs) { ... }, 
    compile: // 傳回一個對象或連接配接函數,如下所示:
    function(tElement, tAttrs, transclude) { 
        return { 
            pre: function(scope, iElement, iAttrs, controller) { ... }, 
            post: function(scope, iElement, iAttrs, controller) { ... } 
           } 
        return function postLink(...) { ... } 
        } 
    }; 
 });      

restrict[string]

restrict是一個可選的參數。用于指定該指令在DOM中以何種形式被聲明。預設值是A,即以屬性的形式來進行聲明。

可選值如下:

E(元素)

<my-directive></my-directive>       

A(屬性,預設值)

<div my-directive="expression"></div>       

C(類名)

<div class="my-directive:expression;"></div>       

M(注釋)

<--directive:my-directive expression-->      
一般考慮到浏覽器的相容性,強烈建議使用預設的屬性就可以即即以屬性的形式來進行聲明。最後一種方式建議再不要求逼格指數的時候千萬不要用。

Code:

angular.module('app',[])
    .directive('myDirective', function () {
            return { 
                restrict: 'E', 
                template: '<a href="http://www.baidu.com">百度</a>' 
            };
        })
HtmlCode:
 <my-directive></my-directive>
      

 效果:

指令&lt;AngularJs&gt;

priority[int]

大多數指令會忽略這個參數,使用預設值0,但也有些場景設定高優先級是非常重要甚至是必須的。例如,ngRepeat将這個參數設定為1000,這樣就可以保證在同一進制素上,它總是在其他指令之前被調用。

terminal[bool]

這個參數用來停止運作目前元素上比本指令優先級低的指令。但同目前指令優先級相同的指令還是會被執行。

例如:ngIf的優先級略高于ngView(它們操控的實際就是terminal參數),如果ngIf的表達式值為true,ngView就可以被正常執行,但如果ngIf表達式的值為false,由于ngView的優先級較低就不會被執行。

template[string or function]

template參數是可選的,必須被設定為以下兩種形式之一:

  •  一段HTML文本;
  • 一個可以接受兩個參數的函數,參數為tElement和tAttrs,并傳回一個代表模闆的字元串。tElement和tAttrs中的t代表template,是相對于instance的。

首先示範下第二種用法:

angular.module('app',[])
    .directive('myDirective', function () {
            return { 
                restrict: 'EAC', 
                template: function (elem, attr) {
                    return "<a href='" + attr.value + "'>" + attr.text + "</a>";
                }
        };
    })      

HtmlCode:(效果同上,不做示範了)

<my-directive value="http://www.baidu.com" text="百度"></my-directive>
        <div my-directive
             value="http://www.baidu.com"
              text="百度"></div>      

templateUrl[string or function]

templateUrl是可選的參數,可以是以下類型:

  • 一個代表外部HTML檔案路徑的字元串;
  • 一個可以接受兩個參數的函數,參數為tElement和tAttrs,并傳回一個外部HTML檔案路徑的字元串。

無論哪種方式,模闆的URL都将通過ng内置的安全層,特别是$getTrustedResourceUrl,這樣可以保護模闆不會被不信任的源加載。 預設情況下,調用指令時會在背景通過Ajax來請求HTML模闆檔案。加載大量的模闆将嚴重拖慢一個用戶端應用的速度。為了避免延遲,可以在部署應用之前對HTML模闆進行緩存。

angular.module('app',[])
    .directive('myDirective', function () {
            return { 
                restrict: 'AEC', 
                templateUrl: function (elem, attr) {
                    return attr.value + ".html";  //當然這裡我們可以直接指定路徑,同時在模闆中可以包含表達式
                }
        };
    })      

replace[bool]

replace是一個可選參數,如果設定了這個參數,值必須為true,因為預設值為false。預設值意味着模闆會被當作子元素插入到調用此指令的元素内部,

例如上面的示例預設值情況下,生成的html代碼如下:

<my-directive value="http://www.baidu.com" text="百度"><a href="http://www.baidu.com">百度</a></my-directive>      

如果設定replace=true

<a href="http://www.baidu.com" value="http://www.baidu.com" text="百度">百度</a>      

據我觀察,這種效果隻有設定restrict="E"的情況下,才會表現出實際效果。

介紹完基本的指令參數後,就要涉及到更重要的作用域參數了...

scope參數[bool or object]

 scope參數是可選的,可以被設定為true或一個對象。預設值是false。

如果一個元素上有多個指令使用了隔離作用域,其中隻有一個可以生效。隻有指令模闆中的根元素可以獲得一個新的作用域。是以,對于這些對象來說scope預設被設定為true。内置指令ng-controller的作用,就是從父級作用域繼承并建立一個新的子作用域。它會建立一個新的從父作用域繼承而來的子作用域。這裡的繼承就不在贅述,和面向對象中的繼承基本是一直的。

 首先我們來分析一段代碼:

<div ng-app="app" ng-init="name= '祖父'">
            <div ng-init="name='父親'">
                第一代:{{ name }}
                <div ng-init="name= '兒子'" ng-controller="SomeController">
                    第二代: {{ name }}
                    <div ng-init="name='孫子'">
                        第三代: {{ name }}
                    </div>
                </div>
            </div>
        </div>       

我們發現第一代,我們初始化name為父親,但是第二代和第三代其實是一個作用域,那麼他們的name其實是一個對象,是以出現的效果如下:

第一代:父親
第二代: 孫子
第三代: 孫子      

我們在修改一下代碼,把第三代隔離開來再看看效果:

<div ng-app="app"ng-init="name= '祖父'">
            <div ng-init="name='父親'">
                第一代:{{ name }}
                <div ng-init="name= '兒子'" ng-controller="SomeController">
                    第二代: {{ name }}
                    <div ng-init="name='孫子'" ng-controller="SecondController">
                        第三代: {{ name }}
                    </div>
                </div>
            </div>
        </div>
      

JsCode:

angular.module('app', [])
        .controller('SomeController',function($scope) {
            
        })
       .controller('SecondController', function ($scope) {
        
    })       

效果如下:

第一代:父親
第二代: 兒子
第三代: 孫子      

在修改下代碼來看看繼承:

<div ng-app="app"ng-init="name= '祖父的吻'">
            <div>
                第一代:{{ name }}
                <div ng-controller="SomeController">
                    第二代: {{ name }}
                    <div  ng-controller="SecondController">
                        第三代: {{ name }}
                    </div>
                </div>
            </div>
        </div>       
第一代:祖父的吻
第二代: 祖父的吻
第三代: 祖父的吻      
如果要建立一個能夠從外部原型繼承作用域的指令,将scope屬性設定為true,簡單來說就是可繼承的隔離,即不能反向影響父作用域。

 再來看個例子:

angular.module('myApp', [])
        .controller('MainController', function ($scope) {
        })
        .directive('myDirective', function () {
            return {
                restrict: 'A',
                scope:false,//切換為{},true測試
                priority: 100,
                template: '<div>内部:{{ myProperty }}<input ng-model="myProperty"/></div>'
            };
        });      

Html代碼:

<div ng-controller='MainController' ng-init="myProperty='Hello World!'">
        外部: {{ myProperty}}
        <input ng-model="myProperty" />
        <div my-directive></div>
    </div>      

 當我們改變scope的值我們會發現

false:繼承但不隔離

指令&lt;AngularJs&gt;

true:繼承并隔離

指令&lt;AngularJs&gt;

{}:隔離且不繼承 

指令&lt;AngularJs&gt;

 transclude

transclude是一個可選的參數。預設值是false。嵌入通常用來建立可複用的元件,典型的例子是模态對話框或導航欄。我們可以将整個模闆,包括其中的指令通過嵌入全部傳入一個指令中。指令的内部可以通路外部指令的作用域,并且模闆也可以通路外部的作用域對象。為了将作用域傳遞進去,scope參數的值必須通過{}或true設定成隔離作用域。如果沒有設定scope參數,那麼指令内部的作用域将被設定為傳入模闆的作用域。

隻有當你希望建立一個可以包含任意内容的指令時,才使用transclude: true。

 我們來看兩個例子-導航欄:

<div side-box title="TagCloud">
        <div class="tagcloud">
            <a href="">Graphics</a>
            <a href="">ng</a>
            <a href="">D3</a>
            <a href="">Front-end</a>
            <a href="">Startup</a>
        </div>
   </div>      
angular.module('myApp', []) 
 .directive('sideBox', function() { 
     return { 
         restrict: 'EA', 
         scope: { 
             title: '@' 
         }, 
         transclude: true, 
         template: '<div class="sidebox"><div class="content"><h2 class="header">' +
             '{{ title }}</h2><span class="content" ng-transclude></span></div></div>' 
         }; 
    });       

這段代碼告訴ng編譯器,将它從DOM元素中擷取的内容放到它發現ng-transclude指令的地方。

再來你看個官網的例子:

angular.module('docsIsoFnBindExample', [])
  .controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
    $scope.name = 'Tobias';
    $scope.hideDialog = function () {
      $scope.dialogIsHidden = true;
      $timeout(function () {
        $scope.dialogIsHidden = false;
      }, 2000);
    };
  }])
  .directive('myDialog', function() {
    return {
      restrict: 'E',
      transclude: true,
      scope: {
        'close': '&onClose'
      },
      templateUrl: 'my-dialog-close.html'
    };
  });      
my-dialog-close.html      
<div class="alert">
  <a href class="close" ng-click="close()">&times;</a>
  <div ng-transclude></div>
</div>      

index.html

<div ng-controller="Controller">
  <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
    Check out the contents, {{name}}!
  </my-dialog>
</div>      
如果指令使用了transclude參數,那麼在控制器無法正常監聽資料模型的變化了。建議在連結函數裡使用$watch服務。

 controller[string or function]

 controller參數可以是一個字元串或一個函數。當設定為字元串時,會以字元串的值為名字,來查找注冊在應用中的控制器的構造函數.

angular.module('myApp', []) 
.directive('myDirective', function() { 
restrict: 'A',  
controller: 'SomeController' 
})       

可以在指令内部通過匿名構造函數的方式來定義一個内聯的控制器

angular.module('myApp',[]) 
.directive('myDirective', function() { 
restrict: 'A', 
controller: 
function($scope, $element, $attrs, $transclude) { 
// 控制器邏輯放在這裡
} 
});       

我們可以将任意可以被注入的ng服務注入到控制器中,便可以在指令中使用它了。控制器中也有一些特殊的服務可以被注入到指令當中。這些服務有:

1. $scope

與指令元素相關聯的目前作用域。

2. $element

目前指令對應的元素。

3. $attrs

由目前元素的屬性組成的對象。 

<div id="aDiv"class="box"></div>
具有如下的屬性對象:
{ 
id: "aDiv", 
class: "box" 
}       

4. $transclude

嵌傳入連結接函數會與對應的嵌入作用域進行預綁定。transclude連結函數是實際被執行用來克隆元素和操作DOM的函數。

angular.module('myApp',[])
 .directive('myLink', function () {
     return {
         restrict: 'EA',
         transclude: true,
         controller:
         function ($scope, $element,$attrs,$transclude) {
             $transclude(function (clone) {              
                 var a = angular.element('<a>');
                 a.attr('href', $attrs.value);
                 a.text(clone.text());
                 $element.append(a);
             });
         }
     };
 });      

html

<my-link value="http://www.baidu.com">百度</my-link>
    <div my-link value="http://www.google.com">谷歌</div>      
僅在compile參數中使用transcludeFn是推薦的做法。link函數可以将指令互相隔離開來,而controller則定義可複用的行為。如果我們希望将目前指令的API暴露給其他指令使用,可以使用controller參數,否則可以使用link來構造目前指令元素的功能性(即内部功能)。如果我們使用了scope.$watch()或者想要與DOM元素做實時的互動,使用連結會是更好的選擇。使用了嵌入,控制器中的作用域所反映的作用域可能與我們所期望的不一樣,這種情況下,$scope對象無法保證可以被正常更新。當想要同目前螢幕上的作用域互動時,可以使用傳入到link函數中的scope參數。

controllerAs[string]

controllerAs參數用來設定控制器的别名,這樣就可以在視圖中引用控制器甚至無需注入$scope。

<div ng-controller="MainController as main">
        <input type="text" ng-model="main.name" />
        <span>{{ main.name }}</span>
    </div>       
angular.module('myApp',[])
   .controller('MainController', function () {
       this.name = "Halower";
   });      
控制器的别名使路由和指令具有建立匿名控制器的強大能力。這種能力可以将動态的對象建立成為控制器,并且這個對象是隔離的、易于測試。

 require[string or string[]]

 require為字元串代表另外一個指令的名字。require會将控制器注入到其所指定的指令中,并作為目前指令的連結函數的第四個參數。字元串或數組元素的值是會在目前指令的作用域中使用的指令名稱。在任何情況下,ng編譯器在查找子控制器時都會參考目前指令的模闆。

  • 如果不使用^字首,指令隻會在自身的元素上查找控制器。指令定義隻會查找定義在指令作目前用域中的ng-model=""
  • 如果使用?字首,在目前指令中沒有找到所需要的控制器,會将null作為傳給link函數的第四個參數。
  • 如果添加了^字首,指令會在上遊的指令鍊中查找require參數所指定的控制器。
  •  如果添加了?^ 将前面兩個選項的行為組合起來,我們可選擇地加載需要的指令并在父指令鍊中進行查找
  • 如果沒有任何字首,指令将會在自身所提供的控制器中進行查找,如果沒有找到任何控制器(或具有指定名字的指令)就抛出一個錯誤

compile【object or function】

compile選項本身并不會被頻繁使用,但是link函數則會被經常使用。本質上,當我們設定了link選項,實際上是建立了一個postLink() 連結函數,以便compile() 函數可以定義連結函數。通常情況下,如果設定了compile函數,說明我們希望在指令和實時資料被放到DOM中之前進行DOM操作,在這個函數中進行諸如添加和删除節點等DOM操作是安全的。

compile和link選項是互斥的。如果同時設定了這兩個選項,那麼會把compile所傳回的函數當作連結函數,而link選項本身則會被忽略。

編譯函數負責對模闆DOM進行轉換。連結函數負責将作用域和DOM進行連結。 在作用域同DOM連結之前可以手動操作DOM。在實踐中,編寫自定義指令時這種操作是非常罕見的,但有幾個内置指令提供了這樣的功能。 

link

compile: function(tEle, tAttrs, transcludeFn) {
 //todo:
 return function(scope, ele, attrs) {
 // 連結函數
 };      

連結函數是可選的。如果定義了編譯函數,它會傳回連結函數,是以當兩個函數都定義時,編譯函數會重載連結函數。如果我們的指令很簡單,并且不需要額外的設定,可以從工廠函數(回調函數)傳回一個函數來代替對象。如果這樣做了,這個函數就是連結函數。

ngModel

它提供更底層的API來處理控制器内的資料,這個API用來處理資料綁定、驗證、 CSS更新等不實際操作DOM的事情,ngModel 控制器會随 ngModel 被一直注入到指令中,其中包含了一些方法。為了通路ngModelController必須使用require設定.

ngModelController常用的元素如下:

  •  1.為了設定作用域中的視圖值,需要調用 ngModel.$setViewValue() 函數。

$setViewValue() 方法适合于在自定義指令中監聽自定義事件(比如使用具有回調函數的jQuery插件),我們會希望在回調時設定$viewValue并執行digest循環。

指令&lt;AngularJs&gt;
angular.module('myApp')
        .directive('myDirective', function() {
            return {
                require: '?ngModel',
                link: function(scope, ele, attrs, ngModel) {
                    if (!ngModel) return;
                    $(function() {
                        ele.datepicker({
                              //回調函數
                            onSelect: function(date) {
                                // 設定視圖和調用 apply
                                scope.$apply(function() {
                                    ngModel.$setViewValue(date);
                                });
                            }
                        });
                    });
                }
            };
        });      
指令&lt;AngularJs&gt;
    • 2.$render方法可以定義視圖具體的渲染方式
    • 3.屬性

      1. $viewValue

      $viewValue屬性儲存着更新視圖所需的實際字元串。

      2. $modelValue

      $modelValue由資料模型持有。 $modelValue和$viewValue可能是不同的,取決于$parser流水線是否對其進行了操作。

      3. $parsers

      $parsers 的值是一個由函數組成的數組,其中的函數會以流水線的形式被逐一調用。ngModel 從DOM中讀取的值會被傳入$parsers中的函數,并依次被其中的解析器處理。

      4. $formatters

      $formatters的值是一個由函數組成的數組,其中的函數會以流水線的形式在資料模型的值發生變化時被逐一調用。它和$parser流水線互不影響,用來對值進行格式化和轉換,以便在綁定了這個值的控件中顯示。

      5. $viewChangeListeners

      $viewChangeListeners的值是一個由函數組成的數組,其中的函數會以流水線的形式在視圖中的值發生變化時被逐一調用。通過$viewChangeListeners,可以在無需使用$watch的情況下實作類似的行為。由于傳回值會被忽略,是以這些函數不需要傳回值。

      6. $error

      $error對象中儲存着沒有通過驗證的驗證器名稱以及對應的錯誤資訊。

      7. $pristine

      $pristine的值是布爾型的,可以告訴我們使用者是否對控件進行了修改。

      8. $dirty

      $dirty的值和$pristine相反,可以告訴我們使用者是否和控件進行過互動。

      9. $valid

      $valid值可以告訴我們目前的控件中是否有錯誤。當有錯誤時值為false, 沒有錯誤時值為true。

      10. $invalid

      $invalid值可以告訴我們目前控件中是否存在至少一個錯誤,它的值和$valid相反。

 未完待續....

     備注:我也是剛剛開始學習,如果你喜歡本文的話,推薦共勉,謝謝!