AngularJS - custom directive

Directive trong AngularJS là gì?

  • Là "trung gian giữa Model và View"
  • Là extension của html syntax
  • Làm nhiệm vụ thực hiện two-way binding giữa View và Model trong AngularJS
  • Ví dụ :
<input type="text" ng-model="name" />
<span>{{name}}</span>
  • ng-model : directive được AngularJS cung cấp sẵn, làm nhiệm cập nhật tức thì giữa giá trị được user nhập vào textbox và scope.

Tóm lại, nhiệm vụ của directive:

  • Phản ánh(reflection)thay đổi của Model(=scope)
  • Cập nhật thay đổi giá trị của scope khi thay đổi View

Ngoài những directive được AngularJS cung cấp sẵn (https://docs.angularjs.org/api/ng#directive), chúng ta hoàn toàn có thể tự custom directive phù hợp với mục đích sử dụng.

Cách tạo và sử dụng một custom directive

Ví dụ về custom directive:

<html ng-app="testDemo">
<head>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript" src="myTest.js"></script>
</head>
<body>
<div custom-directive></div>
</body>
</html>
var testDemo = angular.module('testDemo', []);
testDemo.directive('customDirective', function(){
  return {
    template: '<span>This for Testing Custom Directive</span>'
  };
});

Đoạn code trên sẽ cho ra output như sau:

This for Testing Custom Directive

Từ đoạn code đơn giản ở trên chúng ta có thể thấy:

  • Cú pháp định nghĩa directive: dùng hàm directive của angular.module
  • Quy tắc đặt tên: camelCase (cụ thể ở ví dụ trên là customDirective)
  • Quy tắc gọi: chuyển đổi sang dạng ngăn cách bởi dấu cách - (cụ thể ở ví dụ trên là custom-directive)

Cấu trúc của directive

Một custom directive sẽ đăng ký một factory function. Cụ thể cấu trúc của một factory function của directive như sau:

 testDemo.directive('customDirective', function(){
  // Định nghĩa Directive Object
  return{
    restrict: 'AC',   // 'EA'hoặc 'EAC'
    priority: 0,        // thứ tự ưu tiên
    transclude: true,     // có mang child element hay không
    replace: false,       // có thay thế element này ở template hay không
    template: '<span>{{title}}</span>' // có thể chỉ định file html ở templateUrl
            + '<div ng-transclude></div>',
    scope: {            // tại scope này mà có khai báo object thì sẽ tạo isolate scope
      myDirective: '=', // '=' : two-way binding
      onChange: '&',    // '&': function
      title: '@'        // '@' one-way binding từ parent scope tới local scope
    },
    link: function postLink(scope, element, attrs, ctrl){       //Link function
    },
    // Link function cũng có thể khai báo giống như dưới đây:
    // compile: function(tElement, tAttrs){                     //Compile function
    //  return {
    //    pre: function preLink(scope, element, attrs, ctrl){   //Link function
    //    },
    //    post: function postLink(scope, element, attrs, ctrl){ //Link function
    //    }
    //  };
    //},
    require: '^myParentDirective',
    controller: ['$scope', function($scope){ // định nghĩa Controller
    }]
  };
});

Bây giờ chúng ta sẽ đi sâu vào tìm hiểu cấu trúc của một custom directive:

Factory Function

Giống như angular.modulde(...).controller, chúng ta có thể tiêm những service của Angular vào factory function của directive.

myModule.directive('customeDirective', ['$http', function($http){
  return {
    // xử lý của Ajax sử dụng $http
  };
}]);

restrict

Quy định phạm vi sử dụng trong thẻ html

  • E: Được sử dụng trong tag
  • A: Được sử dụng trong attribute
  • C: Được sử dụng trong class
  • M: Được sử dụng trong tag comment

Ví dụ, khi định nghĩa restrict: 'AC' như trên, ta có thể sử dụng directive này ở trong attribute hoặc class của html (default là attribute):


<div custome-directive></div>
<div class="custom-directive"></div>

priority

Trong trường hợp có nhiều khai báo directive trong một element, compiler sẽ dựa vào priority để quyết định thứ tự dịch giải.

  • Giá trị càng lớn thì độ ưu tiên càng cao
  • Có thể khai báo số âm
  • Trong trường hợp giá trị priority bằng nhau thì thứ tự ưu tiên sẽ không được đảm bảo

transclude

  • Default là false
  • Khi thiết lập là true : có thể chèn child element của directive. Cụ thể như đoạn code dưới đây, customDirective sẽ kết hợp với ngTransclude để hiển thị:
<div custom-directive>
  <h3>...</h3>
  <p>.........</p>
</div>
testDemo.directive('customDirective', function(){
  transclude: true,
  return {
    template: '<div class="head"></div><div class="body"ng-transclude></div>'
  };
});

template/templateUrl

  • template: Khai báo template của custom directive
  • templateUrl: Khai báo đường dẫn của file template
  • Sử dụng được các kí pháp directive của Angular như ng-..., {{...}}

scope

Directive có thể mang scope riêng của mình theo 3 cách như dưới đây

scope: false // cách 1: sử dụng scope ở nơi gọi directive(default)
scope: true // cách 2: kế thừa scope ở nơi gọi directive để sinh ra scope mới
scope: {
  myDirective: '=',
  title: '@',
  onChange: '&'
} // cách 3: sinh ra scope mới độc lập với scope ở nơi gọi directive
```

Ở cách 3 ở trên, sử dụng cú pháp `property: @|=|&` để định nghĩa. Cụ thể ý nghĩa của các ký tự `@`, `=`, `&` là:

- `@` kết nối giá trị property của scope con với các attribute html của directive. Ví dụ trong ` <div my-drirective="my name is {{name}}"></div>, scope:{myDirective:'@'} ` thì giá trị của `scope.myDirective` sẽ là *my name is ...*"(... là giá trị name của scope cha)
- `=` liên kết property của scope con với property của scope cha bẳng two-way binding (Một trong 2 giá trị thay đổi thì giá trị còn lại sẽ được cập nhật ngay). Khai báo này thường được sử dụng trong trường hợp cần sử dụng giá trị của scope cha để quản lý trạng thái của directive.
- `&` Liên kết hàm Expression của Angular tới property của scope con.

Tóm lại, khi nào khai báo ký tự nào ?, thì ta chỉ cần nhớ là : `@` đối với string, `=` đối với handler, `&` đối với function

### link

Có thể nói link đóng vai trò quan trọng nhất của một custom directive vì nó làm nhiệm vụ quản lý binding giữa Model và View

```JavaScript
function postLink(scope, element, attrs, ctrl){
  scope.$watch(...);
  element.on(...);
  scope.$on('$destroy', ...);
}
```

Cụ thể trong đoạn code trên:

- `scope`: là scope object mà kết nối với custom directive này
- `element`: là jQuery object hiển thị element được khai báo trong custom directive này
- `attrs`: làm nhiệm vụ mapping value và name của attribute của một element nào đó
- `ctrl`: Controller của directive cha

Về cơ bản thì chúng ta chỉ sử dụng `scope` và `element` để thực hiện two-way binding. Cụ thể xử lý sẽ theo luồng như sau:

**Thay đổi giá trị của scope khi xảy ra event của DOM**

Ngược lại, khi có sự thay đổi trạng thái của scope sau khi handle event nào đó từ DOM thì link sẽ đăng ký `scope.$apply` với tư cách là event listener của DOM:

```JavaScript
element.find('video').on('pause', function(){
  scope.$apply(function(){
    scope.title=...; // xử lý thay đổi của scope
  });
});
```

Vì biến `element` của hàm `link` là object jQuery nên ta hoàn toàn có thể sử dụng nguyên `jQuery API` như `$.fn.find` hay `$.fn.on` trong các phương thức `regist` hay `find` của `element`

**Xử lý destroy directive**

`scope.$destroy` để destroy object scope có liên quan tới directive. Đoạn code dưới đây là để đăng ký một xử lý endc một directive:

```JavaScript
scope.$on('$destroy', function() {
// Xử lý end
});
```

### controller

- Giống Controller(`ng-controller`) của một ứng dụng Angular

# Nguồn tham khảo

- https://docs.angularjs.org/api/ng#directive
- http://angularjsninja.com/blog/2013/11/20/angularjs-custom-directives/