AngularJS中自定義指令

       AngularJS中除了內置指令,還可以自定義指令。自定義指令和自定義過濾器一樣,有兩種方法:

第一種,在module中配置:$compileProvider.directive('directiveName', function(){ });

       代碼模版爲:

    $compileProvider.directive('', ['', function(){
    // Runs during compile
    return {
        // name: '',
        // priority: 1,
        // terminal: true,
        // scope: {}, // {} = isolate, true = child, false/undefined = no change
        // controller: function($scope, $element, $attrs, $transclude) {},
        // require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements
        // restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
        // template: '',
        // templateUrl: '',
        // replace: true,
        // transclude: true,
        // compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
        link: function($scope, iElm, iAttrs, controller) {
            
        }
    };

       第二種,.directive('directiveName', function(){ });

       代碼模版爲:

        .directive('', ['', function(){
            // Runs during compile
            return {
                // name: '',
                // priority: 1,
                // terminal: true,
                // scope: {}, // {} = isolate, true = child, false/undefined = no change
                // controller: function($scope, $element, $attrs, $transclude) {},
                // require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements
                // restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
                // template: '',
                // templateUrl: '',
                // replace: true,
                // transclude: true,
                // compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
                link: function($scope, iElm, iAttrs, controller) {
                    
                }
            };
        }]);

  可以看到,定義指令會返回一個對象,這個對象裏面包含了各個屬性(選項),這些屬性(選項)就是用來定義指令的。


     指令的名字不要和內置指令衝突,如果指令的名字爲xxx-yyy,那麼設置指令的名字時應爲xxxYyy,即駝峯式的命名。


restrict: 描述指令在模版中的使用方式,包括:元素、樣式類、屬性、註釋,或者以上幾種方式的任意組合。

wKiom1WThHaAx7B6AANtxICdXuk880.bmp


template: 以字符串的形式編寫一個內聯模板。


templateUrl: 加載模版所需要使用的url,如果已經指定了template,此屬性會被忽略。


replace: 如果該屬性爲true,則替換指令所在的元素;如果爲false或者不指定,則追加到元素內部。


例子:

<!DOCTYPE html>
<html ng-app="firstMoudule">

<head>
    <meta charset='utf-8'>
</head>

<body ng-controller="firstController">
    <!-- 使用自定義指令first-tag -->
    <div first-tag></div>
    
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('firstMoudule', [], function($compileProvider, $controllerProvider) {
        $compileProvider.directive('firstTag', function() {
            return {
                restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
                template: '<div>hello pomelo!</div>',
                replace: true
            };
        });
        $controllerProvider.register('firstController', function() {});
    });
    </script>
</body>

</html>


transclude: 當此屬性爲true時,把指令元素中原來的子節點移動到一個新模板的內部。

例子:

<!DOCTYPE html>
<html ng-app="firstMoudule">

<head>
    <meta charset='utf-8'>
</head>

<body ng-controller="firstController">
    <div first-tag>
        old data         
    </div>
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('firstMoudule', [], function($compileProvider, $controllerProvider) {
        $compileProvider.directive('firstTag', function() {
            return {
                restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
                /*transclude爲true時,old data會被放到具有ng-transclude屬性的地方,也就是下面的span*/
                template: '<div>new data <span ng-transclude></span> </div>',
                replace: true,
                transclude: true
            };
        });
        $controllerProvider.register('firstController', function() {});
    });
    </script>
</body>

</html>

輸出

wKioL1WTlxazSiMIAAAPg6Gm0FI926.jpg


priority: 設置指令在模板中的優先級,用整數來表示,數字大的優先級高,先執行。執行順序是相對於元素上的其它指令而言的。如果兩個指令的該值相同,則先定義的先執行。比如內置的ng-repeat該值爲1000。


terminal: 和priority配合使用。如果此屬性爲true,那麼priority比它小的都不會再執行。


例子:

<!DOCTYPE html>
<html ng-app="firstMoudule">

<head>
    <meta charset='utf-8'>
</head>

<body ng-controller="firstController">

    <!-- 同時使用兩個指令 -->
    <div first-tag second-tag></div>
    
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('firstMoudule', [], function($compileProvider, $controllerProvider) {
        $compileProvider.directive('firstTag', function() {
            return {
                restrict: 'A',
                priority: 10
            };
        });
        $compileProvider.directive('secondTag', function() {
            return {
                template: '<div>data</div>',
                replace: true,
                transclude: true,
                priority: 20,
                terminal: true
            };
        });
        $controllerProvider.register('firstController', function() {});
    });
    </script>
</body>

</html>

  注意,這裏同時使用兩個指令,只能有一個裏面template有內容,否則將出錯。second-tag優先級較高,先執行,並且terminal爲true,first-tag不會執行。


complie、link雖然template的方式很有用,但對於指令來說,真正有趣的發生在complielink函數中。這兩個函數是根據Angular創建動態視圖的兩個處理階段來命名的。Angular的初始化過程爲:

    1.加載腳本 加載Angular庫,查找ng-app指令,從而找到應用的邊界。

    2.編譯階段 遍歷DOM結構,標識出模版中註冊的所有指令。對於每一條指令,如果存在complie函數,則調用complie函數得到一個編譯好的template函數,template函數又會調用從所有指令收集來的link函數。編譯階段就是負責模板的轉換。

    3.鏈接階段 爲了讓視圖變成動態的,Angular會對每一條指令運行一個link函數。link函數負責在model和view之間進行動態關聯。

    complie函數僅僅在編譯階段運行一次,而link函數對於指令的每一個實例,都會執行一次。

    對於我們會編寫的大多數指令來說,並不需要對模板轉換,只有編寫link函數即可。有complie函數就不用再定義link函數了。


    complie函數的語法爲:

    這裏返回的相當於link函數。

compile: function(tElement, tAttrs,transclude) {
         return {
           pre: function preLink() { },
           post: function postLink() { }
          };
}

    tElement是當前指令所在的jQuery對象。tAttrs是指令上定義的參數,比如指令fisrt-tag="123",則tAttrs爲123 。這裏transclude是一個函數,如果需要對內容進行變換,而簡單的基於模板的變換並沒有提供這種功能,那麼可以自己寫這個函數。

    如果直接返回,則返回的是postLink,如下:

compile: function(tElement, tAttrs,transclude) {
         return function() { };
}

    preLink在編譯階段之後,指令鏈接子元素之前運行。postLink在所有的子元素指令都鏈接後才運行。如果需要修改DOM結構,應該在postLink裏面做這件事情,如果在preLink裏面做則會破壞綁定過程,並導致錯誤。


例子

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
</head>

<body ng-app="app">
    <div ng-controller="Controller1">
        <div tag1 tag2></div>
    </div>
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('app', [], function($compileProvider) {
            $compileProvider.directive('tag1', function() {
                return {
                    restrict: 'A',
                    template: '<div>hello pomelo!</div>',
                    replace: true,
                    compile: function(tElement, tAttrs, transclude) {
                        console.log('tag1 complie...');
                        return {
                            pre: function preLink() {
                                console.log('tag1 preLink...');
                            },
                            post: function postLink() {
                                console.log('tag1 postLink...');
                            }
                        };
                    }
                };
            });
            $compileProvider.directive('tag2', function() {
                return {
                    restrict: 'A',
                    replace: false,
                    compile: function(tElement, tAttrs, transclude) {
                        console.log('tag2 complie...');
                        return {
                            pre: function preLink() {
                                console.log('tag2 preLink...');
                            },
                            post: function postLink() {
                                console.log('tag2 postLink...');
                            }
                        };
                    }
                };
            });
        })
        .controller('Controller1', function() {});
    </script>
</body>

</html>

wKiom1WTql7gkvnqAAA6leZ1J9I581.jpg


controller、controllerAs、requirecontroller會暴露一個API,通過這個API可以在多個指令之間通過依賴注入進行通信。controllerAs是給controller起一個別名,方便使用。require可以將其它指令傳遞給自己。


例子:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
</head>

<body ng-app="app">
    <div ng-controller="Controller1">
        <div tag1></div>
    </div>
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('app', [], function($compileProvider) {
            $compileProvider.directive('tag1', function() {
                return {
                    restrict: 'A',
                    controller: function($scope) {
                        $scope.data = 'this is the data in controller';
                        this.Data = 'some Data';
                    },
                    controllerlAs: 'Controller',
                    link: function($scope, iElm, iAttrs, Controller) {
                        console.log($scope.data);
                        console.log(Controller.Data);
                    }
                };
            });
        })
        .controller('Controller1', function() {});
    </script>
</body>

</html>

wKiom1WUlwTAwSXPAAA17Qx8XO0858.jpg

  在多個指令間通信還需要require方法。

wKioL1WUmf7DWC2TAARVnCycsyg274.bmp

例子:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
</head>

<body ng-app="app">
    <div ng-controller="Controller1">
        <div parent-tag></div>
    </div>
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('app', [])
        .directive('parentTag', function() {
            return {
                restrict: 'ECMA',
                template: '<div><ul><li ng-repeat="i in players">`i`.`name` `i`.`number`</li></ul><child-tag></child-tag></div>',
                replace: true,
                controller: function($scope) {
                    $scope.players = [{
                        name: 'Mertersacker',
                        number: 4
                    }, {
                        name: 'Koscielny',
                        number: 6
                    }, {
                        name: 'Gabriel',
                        number: 5
                    }];
                    this.addPlayer = function() {
                        $scope.$apply(function() {
                            $scope.players.push({
                                name: 'Chambers',
                                number: 21
                            });
                        });
                    }
                },
                controllerAs: 'parentController'
            };
        })
        .directive('childTag', function() {
            return {
                restrict: 'ECMA',
                require: '^parentTag',
                template: '<button>add player Chambers</button>',
                replace: true,
                link: function($scope, iElm, iAttrs, parentController) {
                    iElm.on('click', parentController.addPlayer);
                }

            }
        })
        .controller('Controller1', function() {});
    </script>
</body>

</html>


wKiom1WUoaegncVvAABZmdV8hKE121.jpg

wKioL1WUo23CU0CRAACA_qq9IZI359.jpg



scope:指明指令所操控數據的作用域。

    如果不指定,scope爲false,會使用指令對應的DOM元素上存在的scope對象;

    如果scope爲true,則會創建一個scope,它繼承了外層控制器中的scope,在繼承樹中,位於當前scope對象上方的所有scope對象的值都可以被讀取。

    如果scope爲{attributeName:'bindingStratry',... ...}即一個對象時,會創建一個獨立的對象。


    對於scope是一個object的情況,有點複雜。此時scope的結構爲:

    scope:{

           attributeName1:'&bindingStratry1',

           attributeName2:'=bindingStratry2',

           attributeName3:'@bindingStratry3'

    }

    當爲&bindingStratry時,表示傳遞一個來自父scope的函數,稍後調用。

例子:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
</head>

<body ng-app="app">
    <div ng-controller="Controller1">


        <div my-tag obj="players"></div>


    </div>
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('app', [])
        .directive('myTag', function() {
            return {
                restrict: 'ECMA',
                controller: function($scope) {
                    console.log($scope.myScopeFn());//myScopeFn必須以函數方法使用
                },
                scope: {
                    myScopeFn: '&obj'
                }
            }
        })
        .controller('Controller1', function($scope) {
            $scope.players = [{
                name: 'Mertersacker',
                number: 4
            }, {
                name: 'Koscielny',
                number: 6
            }, {
                name: 'Gabriel',
                number: 5
            }];
        });
    </script>
</body>

</html>

wKiom1WUxCyQzkN5AACFMOeG70Q919.jpg


   當爲=bindingStratry時,表示傳遞一個來自父scope的屬性,並且是和父scope中對應屬性雙向綁定的。

例子:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
</head>

<body ng-app="app">
    <div ng-controller="Controller1">
    
        <div my-tag obj="players"></div>
        
    </div>
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('app', [])
        .directive('myTag', function() {
            return {
                restrict: 'ECMA',
                controller: function($scope) {
                    <!-- 雙向數據綁定,可以操縱父scope的數據,這裏添加一個元素進去 -->
                    $scope.myScopeAttr.push({
                        name: 'Ozil',
                        number: 11
                    });
                },
                scope: {
                    myScopeAttr: '=obj'
                }
            }
        })
        .controller('Controller1', function($scope) {
            $scope.players = [{
                name: 'Mertersacker',
                number: 4
            }, {
                name: 'Koscielny',
                number: 6
            }, {
                name: 'Gabriel',
                number: 5
            }];
            console.log($scope.players);
        });
    </script>
</body>

</html>

wKiom1WUy2ywC1cuAACd61NMQnE554.jpg

    可以看到,父scope中players的數據改變了。=bindingStratry能雙向數據綁定。


    當爲@bindingStratry時,表示讀取一個來自父scope的屬性,這個屬性只讀,無法改變。

例子:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
</head>

<body ng-app="app">
    <div ng-controller="Controller1">

        <!-- 這裏要特別注意,要放在{{ }}裏 -->
        <div my-tag obj="`players`"></div>


    </div>
    <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script>
    <script type="text/javascript">
    angular.module('app', [])
        .directive('myTag', function() {
            return {
                restrict: 'ECMA',
                controller: function($scope) {
                    console.log($scope.myScopeAttr);
                },
                scope: {
                    myScopeAttr: '@obj'
                }
            }
        })
        .controller('Controller1', function($scope) {
            $scope.players = [{
                name: 'Mertersacker',
                number: 4
            }, {
                name: 'Koscielny',
                number: 6
            }, {
                name: 'Gabriel',
                number: 5
            }];
        });
    </script>
</body>

</html>

wKioL1WU0h-Dwg-tAAA0WDfMl9g890.jpg

   值得一提的是,@bindingStratry是把當前屬性作爲一個字符串傳遞,所以對象等引用類型傳過來會變成字符串,最好還是傳字符串類型的數據過來。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章