AngularJS指令參數詳解—

文章轉自: http://blog.csdn.net/kongjiea/article/details/49840035javascript

指令,很重要

AngularJSjQuery最大的區別在哪裏?我認爲,表如今數據雙向綁定,實質就是DOM的操做形式不同。php

  • jquery經過選擇器找到DOM元素,再賦予元素的行爲;css

  • angularjs則是,將指令與DOM綁定在一塊兒,再擴展指令的行爲。html

因此AngularJS開發最理想的結果就是,在頁面HTML與CSS的設計時,設計工程師只須要關注指令的使用;而在背後的邏輯開發上,架構工程師則是不須要知道如何操做DOM,只須要關注指令背後的行爲要如何實現就行;測試工程師也能夠開發針對指令的單元測試。java

指令就是DOM與邏輯行爲的媒介,本質就是DOM綁定的獨立邏輯行爲函數。jquery

指令難點在於參數

來看看都有哪些angularjs

 
angular.module('app', [])
.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(...) { ... }
        }
    };
 });
 

 

 

剛開始接觸指令的時候,我簡直就是蒙了,這堆參數究竟怎麼用怎麼理解啊。告訴你們個人一個理解方法。
把它們分紅三類:express

  1. 描述指令或DOM自己特性的內部參數數組

  2. 鏈接指令外界、與其餘指令或控制器溝通的對外參數架構

  3. 描述指令自己行爲的行爲參數

內部參數

  • restrict:String,E(元素)<div my-directive="expression"></div> C(類名) M(註釋)<--directive:my-directive expression-->

  • priority: Number,指令執行優先級

  • template: String,指令連接DOM模板,例如「<h1>{{head}}</h1>」

  • templateUrl:String,DOM模板路徑

  • replace: Boolean,指令連接模板是否替換原有元素,

對外參數——scope

scope參數很是重要,本應該是放到最後說明的,可是scope倒是理解其餘參數的關鍵,因此務必先跟你們說清楚。

scope參數的做用是,隔離指令與所在控制器間的做用域、隔離指令與指令間的做用域。

scope參數是可選的,默認值爲false,可選true、對象{};

  • false:共享父域

  • true:繼承父域,且新建獨立做用域

  • 對象{}:不繼承父域,且新建獨立做用域

false、true、{}三者對比

來看個例子

 
<body>
    <div ng-controller='parentCtrl'>
        <h3>指令scope參數——false、true、{}對比測試</h3>
        parent:
        <div>
            <span> {{parentName}}</span>
            <input type="text" ng-model="parentName" />
        </div>
        <br />
        <child-a></child-a>
        <br />
        <child-b></child-b>
        <br />
        <child-c parent-name="parentName"></child-c>
    </div>
    <!--t1指令模板-->
    <script type="text/html" id="t1">
        <div>
            <span>{{parentName}}</span>
            <input type="text" ng-model="parentName" />
        </div>
    </script>
    <script>
        var app = angular.module("app", []);

        app.controller('parentCtrl', function ($scope) {
            $scope.parentName = "parent";
        })

        //false:共享做用域
        app.directive('childA', function () {
            return {
                restrict: 'E',
                scope: false,
                template: function (elem, attr) {
                    return "false:" + document.getElementById('t1').innerHTML;
                }
            };
        });

        //true:繼承父域,並創建獨立做用域
        app.directive('childB', function () {
            return {
                restrict: 'E',
                scope: true,
                template: function (elem, attr) {
                    return "true:" + document.getElementById('t1').innerHTML;
                },
                controller: function ($scope) {
                    $scope.parentName = "parent";

                    //已聲明的狀況下,$scope.$watch監聽的是本身的parentName
                    $scope.$watch('parentName', function (n, o) {
                        console.log("child watch" + n);
                    });

                    //$scope.$parent.$watch監聽的是父域的parentName
                    $scope.$parent.$watch('parentName', function (n, o) {
                        console.log("parent watch" + n);
                    });
                }
            };
        });

        //{}:不繼承父域,創建獨立做用域
        app.directive('childC', function () {
            return {
                restrict: 'E',
                scope: {},
                template: function (elem, attr) {
                    return "{}:" + document.getElementById('t1').innerHTML;
                },
                controller: function ($scope) {
                    console.log($scope);
                }
            };
        });

    </script>
</body>
 

 

 

false參數

本質:子域與父域共享做用域。
特色:父域修改parentName的同時,指令綁定的parentName的元素會被刷新。

反之,指令內部parentName被修改時,父域的parentName一樣會被刷新。

true參數

本質:子域繼承父域,並創建獨立做用域。
特色:

一、在指令已聲明parentName的狀況下,父域parentName變動,指令中parentName不會發生變化。
指令在true參數下,創建了的scope,獨立並隔離與父控制器的scope。

controller: function ($scope) { $scope.parentName = "parent"; } 

反之,指令中parentName變動,父域也不會發生變化。

二、在指令未聲明parentName的狀況下,父域的parentName變動,指令中parentName也會刷新
這種狀況不少時候會被忽略,指令的scope沒有聲明對象時,其元素綁定的仍然是父域的對象。但,一旦指令中Input變動了,對應的獨立scope也會自動聲明該綁定對象,這就回到了第1種狀況。

controller: function ($scope) { //$scope.parentName = "parent"; } 

然而,指令中parentName變動,父域是不會變化的;

三、在指令已聲明parentName的狀況下 ,在指令中監聽父域parentName 的變化無效。但監聽子域parentName的變化有效
獨立子域scope,只能監聽本身的,不能監聽父域的。但經過 $scope.$parent能夠監聽父域。

 
controller: function ($scope) {
    $scope.parentName = "parent" ;

    //已聲明的狀況下,$scope.$watch監聽的是本身的parentName
    $scope.$watch( 'parentName' , function (n, o) {
        console.log("child watch" + n);
    });

    //$scope.$parent.$watch監聽的是父域的parentName
    $scope.$parent.$watch( 'parentName' , function (n, o) {
        console.log("parent watch" + n);
    });
}
 

 

 

四、在指令未聲明parentName的狀況下 ,在指令中監聽父域parentName的變化有效。
這裏就不解釋了,參考第2點,你們能夠動手試一下。

 
controller: function ($scope) {
    //$scope.parentName = "parent";

    //未聲明的狀況下,$scope.$watch監聽的是父域的parentName
    $scope.$watch('parentName' , function (n, o) {
        console.log("child watch" + n);
    });
}
 

 

 

對象{}參數

本質:子域不繼承父域,並創建獨立做用域。
特色:

一、當scope對象爲空對象時,不管是父域parentName,仍是指令子域parentName發生變動,都不會影響到對方。
原理很清楚,就是指令創建的獨立做用域,與父域是徹底隔離的。

scope: {} 

二、當scope對象爲非空對象時,指令會將該對象處理成子域scope的擴展屬性。而父域與子域之間傳遞數據的任務,就是能夠經過這塊擴展屬性完成。

 
<div ng-controller='parentCtrl'>
    parent:
    <p><span>{{name}}</span><input type="text" ng-model="name" /></p>
    <p><span>{{sexy}}</span><input type="text" ng-model="sexy" /></p>
    <p><span>{{age}}</span><input type="text" ng-model="age" /></p>
    <br />

    <!--特別注意:@與=對應的attr,@是單向綁定父域的機制,記得加上{{}};&對應的attrName必須以on-開頭-->
    <child-c my-name="name" my-sexy-attr="sexy" my-age="{{age}}" on-say="say('i m ' + name)"></child-c>
</div>

<!--t1指令模板-->
<script type="text/html" id="t1">
    <div>
        <span>{{myName}}</span>
        <input type="text" ng-model="myName" />
    </div>
    <div>
        <span>{{mySexy}}</span>
        <input type="text" ng-model="mySexy" />
    </div>
    <div>
        <span>{{myAge}}</span>
        <input type="text" ng-model="myAge" />
    </div>
</script>

<script>
    var app = angular.module("app", []);

    app.controller('parentCtrl', function ($scope) {
        $scope.name = "mark";
        $scope.sexy = "male";
        $scope.age = "30";
        $scope.say = function (sth) {
            alert(sth);
        };
    })

    app.directive('childC', function () {
        return {
            restrict: 'E',
            scope: {
                myName: '=',
                mySexy: '=mySexyAttr',
                myAge: '@',
                onSay: '&'
            },
            template: function (elem, attr) {
                return "{}:" + document.getElementById('t1').innerHTML;
            },
            controller: function ($scope) {
                console.log($scope.myName);
                console.log($scope.mySexy);
                console.log($scope.myAge);
                $scope.onSay();
            }
        };
    });

</script>
 

 

 

@(or @Attr)綁定策略——本地做用域屬性,使用@符號將本地做用域同DOM屬性的值進行綁定。指令內部做用域可使用外部做用域的變量。(單向引用父域對象)

<child-c my-age="{{age}}"></child-c> 

ps:@ 是單向綁定本地做用域,記得加上{{}}

scope: { myAge: '@', } 

= (or =Attr)綁定策略——雙向綁定:經過=能夠將本地做用域上的屬性同父級做用域上的屬性進行雙向的數據綁定。就像普通的數據綁定同樣,本地屬性會反映出父數據模型中所發生的改變。(雙向引用父域對象)

 
<child-c onSay="name"></child-c>
 

 

 

ps:=策略不須要加上{{}}進行綁定

 
scope: {          
     myName: '=',
}
 

 

 

& (or &Attr)綁定策略——經過&符號能夠對父級做用域進行綁定,以便在其中運行函數。(調用父域函數)

 
<child-c on-say="say('i m ' + name)"></child-c>
 

 

 

ps:&對應的attrName必須以on-開頭

scope: { onSay: '&', } 

父域綁定調用函數及傳參

 
app.controller('parentCtrl', function ($scope) {
     $scope.say = function (sth) {
         alert(sth);
     };
})
 

 

 

ps特別注意:@與=對應的attr,;

 
<child-c my-name="name" my-sexy-attr="sexy" my-age="{{age}}" on-say="say('i m ' + name)"></child-c>
 

 

 

總結下來,scope擴展對象,既可以解耦父域與子域共域的問題,也可以實現指令與外界通信的問題,是Angular開發指令化模塊化的重要基礎。在日後的章節,我會向你們介紹指令化開發的更多實例。

對外參數——require

scope是指令與外界做用域通信的橋樑,而require是指令與指令之間通信的橋樑。這個參數最大的做用在於,當要開發單指令沒法完成,須要一些組合型指令的控件或功能,例如日期控件,經過require參數,指令能夠得到外部其餘指令的控制器,從而達到交換數據、事件分發的目的。

使用方法: 在link函數第4個參數ctrl中獲取注入外部指令的控制器,若是require爲String,ctrl爲對象,若是require是數組,ctrl爲數組。

 
require: '^teacher1',
link: function ($scope, $element, $attrs, ctrl) {
    //ctrl指向teacher1指令的控制器
}
 

 

 

?策略——尋找指令名稱,若是沒有找到,link函數第4個參數爲null;若是沒有?,則報錯。

^ 策略——在自身指令尋找指令名稱的同時,向上父元素尋找;若是沒有^,則僅在自身尋找。
以下例子,指令teacher及自身,可是不能找到相鄰兄弟的<div teacher> <student-a></student-a> <student-b></student-b> </div>

完整例子

 
<body>
    <div teacher>
        {{name}}
        <student-a></student-a>
        <student-b></student-b>
    </div>
    <script>
        var app = angular.module("app", []);

        //studentA——require指向父級指令teacher
        app.directive('studentA', function () {
            return {
                require: '?^teacher',
                scope: {},
                template: '<div>A`s teacher name: <span>{{teacherName}}</span></div>',
                link: function ($scope, $element, $attrs, ctrl) {
                    //獲取teacher指令控制器,並調用其方法sayName()
                    $scope.teacherName = ctrl.sayName();
                }
            };
        });

        //studentB——require指向父級指令teacher,及指令studentA
        //可是,因爲不能得到兄弟,也沒有采起?策略,致使報錯
        app.directive('studentB', function () {
            return {
                require: ['?^teacher', 'studentA'],
                scope: {},
                template: '<div>B`s teacher name: <span>{{teacherName}}</span></div>',
                link: function ($scope, $element, $attrs, ctrl) {
                    $scope.teacherName = ctrl.sayName();
                }
            };
        });

        app.directive('teacher', function () {
            return {
                restrict: 'A',
                controller: function ($scope) {
                    $scope.name = "Miss wang";

                    //擴展控制器的方法sayName,目的是讓外部內獲取控制器內部數據
                    this.sayName = function () {
                        return $scope.name;
                    };
                }
            };
        });
    </script>
</body>
 

 

 

既然require能夠獲取外部指令,那Angular原生指令應該也是可以獲取。其中最普遍應用的就是 行爲參數——link與controller

爲何要把link與controller兩個參數放到一塊兒?
由於不少童鞋會把它們錯誤地混淆使用,包括我本身。

link與controller都是描述指令行爲的參數,但它們是要描述的行爲是徹底不一樣的類型。

controller語法  controller自己的意義就是賦予指令控制器,而控制器就是定義其內部做用域的行爲的。

因此controller要描述的是:指令的做用域的行爲。

//指向匿名控制器 controller: function ($scope) { }, //指向控制器mainCtrl controller: "mainCtrl" 

link語法  link名稱是連接函數,啥意思,好像挺難理解。因此在解釋連接函數以前,先要說一下Angular的初始化對於指令究竟作了什麼。

Angular在剛從HTTP Response接收靜態素材之初,會首先去分析母頁HTML中有哪些原生指令或自定義指令,而後再去加載指令的template模板HTML,而template模板中又去加載本身的指令模板,如此類推,直到Angular找到了全部的指令及模板,造成模板樹,並返回模板函數,提供給下一階段進行數據綁定。

<body>
    <stu1 ></ stu1>
    <script >
        var app = angular.module("app" , []);

        app.directive( 'stu1' , function () {
            return {
                restrict: 'E' ,
                template: "<p>1</p><stu2></stu2>" ,
                link: function (scope) {
                    console.log( 'stu1 running' );
                }
            };
        });

        app.directive( 'stu2' , function () {
            return {
                restrict: 'E' ,
                template: "<p>2</p><stu3></stu3>" ,
                link: function (scope) {
                    console.log( 'stu2 running' );
                }
            };
        });

        app.directive( 'stu3' , function () {
            return {
                restrict: 'E' ,
                template: "<p>3</p>" ,
                link: function (scope) {
                    console.log( 'stu3 running' );
                }
            };
        });

    </script >
</ body>

console output

stu3 running
stu2 running
stu1 running

注意以上例子,在第一個斷點 簡單來講就是:

    加載模板,造成DOM模板樹

    @@@@

    數據綁定

@@@@是啥?沒錯,就是link連接函數,它會在造成模板樹以後,在數據綁定以前,從最底部指令開始,逐個指令執行它們的link函數。

在這個時間節點的link函數,操做DOM的性能開銷是最低,很是適合在這個時機執行DOM的操做,例如鼠標操做或觸控事件分發綁定、樣式Class設置、增刪改元素等等。

因此link就是描述指令元素操做行爲。

link: function (scope, element, attr, ctrl) {

    element.bind("click", function () {
        console.log("綁定點擊事件");
    });

    element.append("<p>增長段落塊</p>");

    //設置樣式
    element.css("background-color", "yellow");

    //不推薦,在link中賦予scope行爲
    scope.hello = function () {
        console.log("hello");
    };
}

同理,在link中定義$scope行爲是不推薦的。

這樣想一想,對於controller與link,就明白了。但還有一個問題,它們倆的執行順序是?答案是先controller,後link。

放到全局順序就是:

    執行controller,設置各個做用域scope

    加載模板,造成DOM模板樹

    執行link,設置DOM各個行爲

    數據綁定,最後scope綁上DOM

例子

    <div student>
        {{name }}
    </div>
    <script>
        var app = angular.module("app", []);

        app.directive('student', function () {
            return {
                restrict: 'A',
                controller: function ($scope) {
                    $scope.name = "tgor";

                    console.log('controller running');
                },
                link: function (scope, el) {
                    el.append("<p>hello</p>");

                    console.log('link running');
                }
            };
        });

    </script>

 

總結

總結就是不想總結,還有compile、controllerAs……沒有講。寫得累死,但願能給你們一些幫助,同時也能夠將本身的錯誤暴露出來,望你們指正。
下一節,會跟你們分享一下指令在實戰中的一些實例。

end

相關文章
相關標籤/搜索