AngularJS 中除了可以用預設的 宣告式語法(Directive) 之外,也可以自訂自己需要用的 宣告式語法

例如自訂一個 sayhello tag

<div ng-app="BallApp">
    <h2>Angular Test</h2>
    <sayhello message="Good, ">
        Tom
    </sayhello>
    <sayhello>
        Johnson
    </sayhello>
</div>

Directive Definition Object

要自訂 directive 必須透過一個回傳物件來定義你的操作
這邊紀錄幾個比較常用的 option

priority
priority 是用來負責控制優先權,決定哪一個語法要先執行,預設是 0 (ng-repeat 的 priority 是 1000)

terminal
如果設為true,在有設priority的多個directive下,執行到此directive之後就會停止

restrict
restrict 有四個選擇可以讓你決定自訂的語法要是什麼型態

  • E - 元素(Element name):
    <my-directive></my-directive>
  • A - 屬性(Attribute) (default):
    <div my-directive="exp"></div>
  • C - 類別(Class):
    <div class="my-directive: exp;"></div>
  • M - 註解(Comment):
    <!-- directive: my-directive exp -->

template
使用template render html
HTML

<h2>View Render</h2>
<div ng-controller="NameCtrl">
    <sayhello/>
</div>

Javascript

var ballApp = angular.module('BallApp', []);

ballApp.controller('NameCtrl', function($scope) {
    $scope.name = 'Johnson';
});

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        template: '<div>Hello {{ name }}</div>',
        // 也可以使用 templateUrl 指定檔案
        // templateUrl: 'test.html'
    };
});

replace
若為true則會用template取代原本的HTML元素,若為false會將元素塞到到原本的tag裡面

範例
HTML

<sayhello>Johnson</sayhello>

Javascript

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        template: '<div>Hello, Johnson</div>',
        replace: true
    };
});

transclude
設為true可以將原本的HTML的內容移到template定義的元素裡

範例
HTML

<sayhello>Johnson</sayhello>

Javascript

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        // 透過 ng-transclude 決定 HTML 內容移入的位置
        template: '<div>Hello, <span ng-transclude></span></div>',
        transclude: true
    };
});

scope

當 scope 為 false 時,會使用原先 Controller 的 scope(default)

<div ng-controller="NameCtrl">
    <input type="text" ng-model="name" placeholder="Enter Name"/>
    <sayhello></sayhello>
</div>
var ballApp = angular.module('BallApp', []);

ballApp.controller('NameCtrl', function($scope) {
    $scope.name = 'Johnson';
});

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        scope: false,
        template: '<div xxx-attr="name">Hello, {{ name }}</div>',
        replace: true,
        link: function(scope, element, attrs) {
            scope.name = 'Woody';
        }
    };
});

當 scope 為 true 時,會建立一個繼承 parent scope 的物件

<div ng-controller="NameCtrl">
    <input type="text" ng-model="name" placeholder="Enter Name"/>
    <sayhello>
</div>
var ballApp = angular.module('BallApp', []);

ballApp.controller('NameCtrl', function($scope) {
    $scope.name = 'Johnson';
    $scope.message = 'Hello';
});

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        scope: true,
        template: '<div xxx-attr="name">Hello, {{ name }}</div>',
        replace: true,
        link: function(scope, element, attrs) {
            scope.name = 'Woody';
        }
    };
});

當 scope 為物件時,會建立新的 isolate scope

<div ng-controller="NameCtrl">
    <input type="text" ng-model="name" placeholder="Enter Name"/>
    <sayhello>
</div>
var ballApp = angular.module('BallApp', []);

ballApp.controller('NameCtrl', function($scope) {
    $scope.name = 'Johnson';
});

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        // 這種狀況下因為 scope 是另外獨立的 scope, 因此 name 不會有值
        scope: {},
        template: '<div xxx-attr="{{ name }}">Hello, {{ name }}</div>',
        replace: true,
        link: function(scope, element, attrs) {
            // dosomething
        }
    };
});

Text Binding

var ballApp = angular.module('BallApp', []);

ballApp.controller('NameCtrl', function($scope) {
    $scope.name = 'Johnson';
});

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        scope: {
            // 透過 @ 將 Local scope 或 Parent scope 的值存進特定的 HTML Attribute,並當成 Local scope 變數
            // localName: @Attribute
            messageAttr: '@messageAttr',
            // 等同於 localName: @localName
            id: '@'
        },
        template: '<div id="{{ name }}" message-attr="Hi,">{{ messageAttr }} {{ id }}</div>',
        replace: true,
        link: function(scope, element, attrs) {
            // dosomething
        }
    };
});

Two-way Binding

var ballApp = angular.module('BallApp', []);

ballApp.controller('NameCtrl', function($scope) {
    $scope.name = 'Johnson';
});

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        // = 的用法與 @ 相同,但會與 Parent scope 做 Twoway Binding
        scope: {
            isolateName: '=xxxAttr',
            // 當使用 = 找不到對應的 Attribute 時會有NON_ASSIGNABLE_MODEL_EXPRESSION 的 exception,可以使用 ? 避開這個判斷
            ggName: '=?ggAttr'
        },
        // HTML 部份只要填入 parent scope model
        template: '<div xxx-attr="name">Hello, {{ isolateName }}</div>',
        replace: true,
        link: function(scope, element, attrs) {
            // 這裡的改變後,NameCtrl 的 name 也會變成 Woody
            scope.isolateName = 'Woody';
        }
    };
});

Method binding

<div ng-controller="NameCtrl">
    <input type="text" ng-model="name" placeholder="Enter Name"/>
    <button ng-click="alertMessage()">alert</button>
    <!-- use NameCtrl method 並傳入自訂參數 -->
    <sayhello test="callHome(str)">
</div>
var ballApp = angular.module('BallApp', []);

ballApp.controller('NameCtrl', function($scope) {
    $scope.name = 'Johnson';
    $scope.alertMessage = function() {
        alert("Message");
    };

    $scope.callHome = function(str) {
        alert(str);
    };
});

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        scope: {
            // 透過 & binding method
            localFn: '&alertMessage',
            // binding sayhello tag 中的 test Attribute,並定義傳入參數
            test: '&'
        },
        template: '<div alert-message="alertMessage()">Hello, {{ name }}<button ng-click="localFn()">alert</button><button ng-click="test({str:name})">call home</button></div>',
        replace: true,
        link: function(scope, element, attrs) {
            scope.name = 'Woody';
        }
    };
});

controller
可以為 Directive 定義一個controller,可透過 Dependency Injection 帶入 $scope、$element、$attrs、$transclude

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        controller: function($scope) {
            $scope.message = 'Hello';
        },
        template: '<div>{{ message }}, Johnson</div>',
        replace: true
    };
});

require
require 是設定 directive 之間的互動

<sayhello-outer>
    <sayhello-inner></sayhello-inner>
</sayhello-outer>
var ballApp = angular.module('BallApp', []);

ballApp.controller('NameCtrl', function($scope) {
    $scope.name = 'Johnson';
});

ballApp.directive('sayhelloOuter', function() {
    return {
        scope: {},
        restrict: 'E',
        controller: function($scope, $compile, $http) {
            // this 代表該 controller
            this.show = function(childScope) {
                console.log(childScope.message);
            }
        }
    };
});

ballApp.directive('sayhelloInner', function() {
    return {
        scope: {},
        restrict: 'E',
        // ^ 會去尋找 parent controller,若找不到會 throws error
        // ?^ 與 ^ 相同,但找不到不會 throws error
        require: '^sayhelloOuter',
        link: function(scope, elem, attrs, controllerInstance) {
            scope.message = "Hello Johnson";
            controllerInstance.show(scope);
        }
    };
});

compile
compile 是針對我們自訂的 directive 進行運作上的調整
輸入的參數

function compile(tElement, tAttrs, transclude) { ... }

範例
HTML

<form-input type="button" value="Click"></form-input>
<form-input></form-input>

Javascript


ballApp.directive('formInput', function() {
    return {
        restrict: 'E',
        // 將 formInput compile 成 一般 HTML input
        compile: function(tElement, tAttrs) {
            var type = tAttrs.type || 'text';
            var val = tAttrs.value;
            var htmlText = '<input type=' + type + ' value=' + val  + ' />';
            tElement.replaceWith(htmlText);
        }
    };
});

link
link 是負責處理資料的binding和初始化
這個方法只有在 compile 沒有定義時才可以使用

輸入的參數

function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }

範例

ballApp.directive('sayhello', function() {
    return {
        restrict: 'E',
        template: '<div>{{ message }}, {{ name }}</div>',
        link: function(scope, element, attrs) {
            // 取得 attributes 內容
            var str = (attrs.message === undefined) ? ('Hello') : (attrs.message);
            scope.message = str;
        }
    };
});

注意
當 Directive 命名有大小寫時,例如:myDirective,HTML使用時要是 my-directive

Categories: AngularJS