+1

Complex Directives in AngularJS

Hiện tại AngularJS đã khá quen thuộc với hầu hết các web developer rồi, trên viblo đã có rất nhiều các bài viết giới thiệu các kiến thức tổng quan cũng như từng chuyên đề nhỏ như filter, service, directive trong AngularJS.Hôm nay chúng ta sẽ cùng nhau tìm hiểu về một vấn đề cũng khá thú vị khác: Complex Directive, bài viết sẽ giới thiệu ví dụ cụ thể để chúng ta hiểu và sử dụng complex directive hiệu quả trong các project của mình.

Preparing the Example Project

Trước tiên chúng ta có một danh sách như sau:

<html ng-app="exampleApp">
  <head>
    <title>Directives</title>
    <script src="angular.js"></script>
    <link href="bootstrap.css" rel="stylesheet" />
    <link href="bootstrap-theme.css" rel="stylesheet" />
    <script>
      angular.module("exampleApp", [])
      .directive("unorderedList", function () {
      return function (scope, element, attrs) {
      var data = scope[attrs["unorderedList"]];
      var propertyExpression = attrs["listProperty"];
      if (angular.isArray(data)) {
      var listElem = angular.element("<ul>");
      element.append(listElem);
      for (var i = 0; i < data.length; i++) {
      var itemElement = angular.element("<li>")
      .text(scope.$eval(propertyExpression, data[i]));
      listElem.append(itemElement);
      }
      }
      }
      }).controller("defaultCtrl", function ($scope) {
      $scope.products = [
      { name: "Apples", category: "Fruit", price: 1.20, expiry: 10 },
      { name: "Bananas", category: "Fruit", price: 2.42, expiry: 7 },
      { name: "Pears", category: "Fruit", price: 2.02, expiry: 6 }
      ];
      })
    </script>
  </head>
  <body ng-controller="defaultCtrl">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3>Products</h3>
      </div>
      <div class="panel-body">
        <div unordered-list="products" list-property="price | currency"></div>
      </div>
    </div>
  </body>
</html>

Các thuộc tính được định nghĩa bơỉ Directive Definition Objects

  • compile: Chỉ định một compile function
  • controller: Tạo một controller function cho directive
  • link: Chỉ định một link function cho directive, tham khảo thêm trong phần Defining How the Directive Can Be Applied
  • replace: Chỉ định nội dung của template thay thế phần tử mà directive đã được apply
  • require: Khai báo một dependence trong một controller
  • scope: Tạo mới một scope hoặc một scope bị cô lập cho directive
  • template: Khai báo một template sẽ được insert trong HTML document
  • templateUrl: Định nghĩa một external template sẽ được insert trong HTML document
  • transclude: Chỉ định các directive sẽ được sử dụng để wrap nội dung tùy ý

Các thuộc tính rất dễ hiểu phải không nào.

Defining How the Directive Can Be Applied

Khi bạn return một link function, bạn create một directive chỉ được apply như là một attribute.Đây là cách mà hầu hết các directive chỉ thị AngularJS được áp dụng, nhưng bạn có thể sử dụng thuộc tính restrict để thay đổi mặc định và tạo các chỉ thị có thể được áp dụng theo những cách khác nhau.Trong ví dụ sau chúng ta có thể thấy rõ được rằng ta đã update unorderedList directive mà nó được định nghĩa với một đối tượng và sử dụng thuộc tính restrict

<script>
  angular.module("exampleApp", [])
    .directive("unorderedList", function() {
      return {
        link: function(scope, element, attrs) {
          var data = scope[attrs["unorderedList"] || attrs["listSource"]];
          var propertyExpression = attrs["listProperty"] || "price | currency";
          if (angular.isArray(data)) {
            var listElem = angular.element("<ul>");
            if (element[0].nodeName == "#comment") {
              element.parent().append(listElem);
            } else {
              element.append(listElem);
            }
            for (var i = 0; i < data.length; i++) {
              var itemElement = angular.element("<li>")
                .text(scope.$eval(propertyExpression, data[i]));
              listElem.append(itemElement);
            }
          }
        },
        restrict: "EACM"
      }

    }).controller("defaultCtrl", function($scope) {
      $scope.products = [{
          name: "Apples",
          category: "Fruit",
          price: 1.20,
          expiry: 10
        },
        {
          name: "Bananas",
          category: "Fruit",
          price: 2.42,
          expiry: 7
        },
        {
          name: "Pears",
          category: "Fruit",
          price: 2.02,
          expiry: 6
        }
      ];
    })
</script>

Chúng ta đã thay đổi factory function method để nó trả về một đối tượng, đó là đối tượng được định nghĩa của chúng ta, chứ không đơn thuần chỉ là một link function.Dẫu vậy chúng ta vẫn cần một link function cho directive của chúng ta, ok tất nhiên rồi vì thế ta chỉ định function để liên kết thuộc tính của đối tượng được khai báo.Thay đổi tiếp theo là thêm thuộc tính giới hạn vào đối tượng định nghĩa.Điều này cho ta thấy rằng có 4 cách trong AngularJS có thể sử dụng cho custom directive, với mỗi kiểu sử dụng được đại diện bởi một trong những kí tự được mô tả sau đây:

  • E: Cho phép directive có thể apply một element
  • A: Cho phép directive có thể apply một attribute
  • C: Cho phép directive có thể apply một class
  • M: Cho phép directive có thể apply một comment

Như vậy chúng ta có thể thấy rằng custom directive có thể được apply với 4 cách kể trên: một element, một attribute, một class, và một comment Sau đây chúng ta sẽ đi vào cách sử dụng của từng loại cụ thể nhé:

Applying the Directive as an Element

AngularJS convention sử dụng elements cho directives để quản lí một template qua templatetemplateUrl định nghĩa thuộc tính và khai báo trong mục Using Directive Templates.Đó là một convention về cách sử dụng, tuy nhiên bạn có thể apply một vài custom directive như là một element bằng cách include chữ cái E trong value cho restrict khai báo thuộc tính.Trong ví dụ sau đây bạn có thể thấy được rằng: làm thế nào để apply example directive như là một element

...
<div class="panel-body">
  <unordered-list list-source="products" list-property="price | currency" />
</div>
...

Như vậy ta đã apply một unordered-list element đã được config sử dụng attribute.Nó yêu cầu sự thay đổi link function cho directive bởi vì source của data phải được khai báo với attribute mới nếu không có sẵn unordered-list attribute value

...
var data = scope[attrs["unorderedList"] || attrs["listSource"]];
...

Applying the Directive as an Attribute

AngularJS convention apply tất cả các directive như là attributes, và dưới đây sẽ là mô tả cách apply custom directive như là một attribute

...
<div class="panel-body">
  <div unordered-list="products" list-property="price | currency"></div>
</div>
...

Chúng ta không cần thay đổi điều gì để link function hỗ trợ apply directive trong cách này, tất nhiên rồi, vì chúng ta đã viết code với cách tiếp cận đó.

Applying the Directive as a Class Attribute Value

Bất cứ khi nào có thể, bạn nên apply các directive như elements hoặc attributes, hiệu quả là không kém vì những phương pháp tiếp cận này giúp bạn dễ dàng nhìn thấy nơi các directives được apply.Bạn cũng có thể apply các directive như là giá trị cho class attribute, có thể hữu ích khi bạn đang thử tích hợp AngularJS vào HTML được tạo ra bởi một ứng dụng không dễ dàng thay đổi.Cùng nhau xem đoạn code mô tả dưới đây nhé:

...
<div class="panel-body">
  <div class="unordered-list: products" list-property="price | currency"></div>
</div>
...

Chúng ta thiết lập giá trị của class attribute tới directive name.Ta muốn cung cấp một configuration value cho directive, vì thế chúng ta theo dõi tên với một dấu 2 chấm và giá trị.AngularJS sẽ trình diễn thông tin này như thể có một thuộc tính không có thứ tự liệt kê phần tử, giống như là cách chúng ta đã applied một directive như một attribute.Chúng ta đã linh hoạt một chút và định nghĩa một thuốc tính list-property trên phần tử mà nội dung đã được directive apply. Tất nhiên, nếu ta đã có thể làm được điều đó trong một project thực sự, sau đó sẽ không cần áp dụng directive qua class attribute. Trong một dự án thực sự ta sẽ phải làm điều gì đó như sau đây:

...
<div class="panel-body">
  <div class="unordered-list: products, price | currency"></div>
</div>
...

Điều này sẽ khiến AngularJS cung cấp chức năng directive link function với một value cho unorderedList attribute của products, price | currency, và sẽ chịu trách nhiệm phân tích các giá trị trong chức năng liên kết.

Applying the Directive as a Comment

Cuối cùng là apply một directive như một HTML comment.Đây là phương án cuối cùng, và bạn nên cố gắng sử dụng một trong các tùy chọn khác bất cứ khi nào có thể.Sử dụng comment để áp dụng một directive làm cho các nhà phát triển khác khó đọc HTML, ta sẽ không mong comment để có ảnh hưởng đến chức năng ứng dụng. Nó cũng có thể gây ra vấn đề với các build tools bỏ qua comment để giảm kích thước tập tin để triển khai.Hãy cùng xem xét ví dụ sau:

...
<div class="panel-body">
  <!-- directive: unordered-list products
</div>
...

Comment phải bắt đầu bằng từ directive, tiếp theo là dấu hai chấm, tên của directive, và một đối số cấu hình tùy chọn.Cũng như trong phần trước, tôi không muốn bị hút vào thế giới của chuỗi phân tích cú pháp, vì vậy tôi đã sử dụng các đối số tùy chọn để xác định nguồn của dữ liệu và cập nhật các chức năng liên kết để thiết lập một mặc định cho biểu thức thuộc tính, như sau:

...
var propertyExpression = attrs["listProperty"] || "price | currency";
...

Tôi đã phải thay đổi cách mà các link function hoạt động để hỗ trợ các phương pháp tiếp cận comment.Cho người khác tiếp cận, ta thêm nội dung vào phần tử mà directive được apply, nhưng điều đó sẽ không hoạt động cho một comment.Thay vào đó, tôi sử dụng jqLite để định vị và hoạt động trên parent của các thành phần comment, như sau:

...
if (element[0].nodeName == "#comment") {
  element.parent().append(listElem);
} else {
  element.append(listElem);
}
...

Đoạn code này được hack một chút và dựa vào thực tế là jQuery / jqLite đối tượng được trình bày như là một mảng của HTMLElement object, đó là trình duyệt của DOM đại diện của các yếu tố HTML.Ta nhận được yếu tố đầu tiên trong đối tượng jqLite bằng cách sử dụng một mảng chỉ số bằng không và gọi thuộc tính nodeName, nó cho ta biết loại phần tử nào đã được directive apply.Nếu đó là một comment, ta sử dụng phương pháp parent jqLite để có được các yếu tố có chứa là comment và thêm ul element của ta vào nó.Đây là một cách tiếp cận khá xấu xí và là một lý do khác tại sao lại sử dụng comment đã được directive apply cần tránh.

Các bạn có thể tham khảo cuối sách Pro AngularJS chapter 15 để hiểu hơn về ví dụ minh họa và cách dùng của Custom Directive nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí