12/08/2018, 13:41

Scope của directive trong AngularJS

Scope của directive trong AngularJS Hôm nay mình sẽ đề cập đến một vấn đề rất quan trọng khi làm việc với angular JS đó chính là scope của directive trong AngularJS. Cho dù bạn chưa biết hay biết rõ về angularJS thì mình tin chắc nó cũng sẽ có ích cho các bạn. DIRECTIVE là gì? Với những ...

Scope của directive trong AngularJS

Hôm nay mình sẽ đề cập đến một vấn đề rất quan trọng khi làm việc với angular JS đó chính là scope của directive trong AngularJS. Cho dù bạn chưa biết hay biết rõ về angularJS thì mình tin chắc nó cũng sẽ có ích cho các bạn.

scopes_in_directives.png

DIRECTIVE là gì?

Với những bạn nào đã làm việc với angularJS đều biết rằng directive là một trong những tính năng mạnh mẽ nhất của AngularJS. Mình sẽ nhắc lại, cũng như giới thiệu qua 1 chút cho các bạn chưa biết về Directive.

Đây là một thành phần mở rộng HTML, là các thuốc tính của các thẻ HTML mà AngularJS có định nghĩa thêm. Nó tuân thủ theo nguyên tắc của Angular đó là luôn bắt đầu bằng tiền tố ng. Khi khai báo “ng-prefix”, phần prefic chính là tên của directive mà chúng ta muốn sử dụng. Một số directive thông dụng như :

  • ng-app : Dùng để bắt đầu một ứng dụng AngularJS.
  • ng-init : Dùng để khởi tạo dữ liệu cho ứng dụng.
  • ng-mode : Dùng định nghĩa model như là một biến có thể sử dụng trong AngularJS.
  • ng-repeat : Dùng để lặp lại các phần tử HTML cho mỗi item trong một tập hợp.

Và còn nhiều directive có sẵn khác nữa. Ngoài ra bạn còn có thể tự tạo cho mình các directive riêng. Để bạn có thể hiểu rõ và nắm chắc hơn về directive thì mình gợi ý là bạn nên đọc cuốn sách Angular JS của Alex Vanston (link :

https://www.packtpub.com/web-development/angularjs-directives)

Nó đã bao gồm tất cả mọi thứ bạn cần biết về directive. Còn trong bài viết này mình sẽ thảo luận về một khía cạnh của directive gọi là “Directive scope”

Scope trong AngularJS

Không giống như những framwork MVC khác, AngularJS không có những class cụ thể hoặc những hàm để tạo ra các đối tượng “model”. Thay vào đó AngularJS mở rộng các đối tượng Javascript gốc với các phương thức và thuộc tính được tùy chỉnh. Các đối tượng này cũng được biết đến như là scope, làm việc kết nối giữa view và các phần còn lai (directives, controller và services) bên trong ứng dụng Angular.

Mỗi khi một ứng dụng AngularJS được chạy thì một đối tượng là rootScope được khởi tạo. Mỗi scope được khởi tạo bởi các controller, directives và services được kế thừa từ rootScope. AngularJS documment là một trong những nguồn tài liệu tốt nhất để hiểu cách mà các scope kế thừa và làm việc, bạn có thể thêm khảo thêm tại đó.

(https://github.com/angular/angular.js/wiki/Understanding-Scopes)

Scope bên trong một directive

(Giả sử rằng bạn đã biết tạo một directive đơn giản)

Tất cả các directives đều có một scope liên kết với chúng. Các scope này được sử dụng để truy cập dữ liêu/phương thức ở bên trong template và liên kết chức năng. Mặc định là nếu bạn không tùy chỉnh, thiết lập thì các directives không tạo ra scope của chúng. Chúng sẽ dử dụng scope của parent như là scope của chúng (thường là controller).

AngularJS cũng cho phép chúng ta thay đổi scope mặc định của directives bằng cách truy cap vào một đối tượng để cấu hình được gọi là directive definition object (hay được gọi tắt là DDO). Một DDO là một đối tượng javascript cơ bản được sử dụng để cấu hình cho các behaviour, template..etc của directives. Bạn có thể đọc qua AngularJS documment để về DDO tại

https://docs.angularjs.org/guide/directive

Ví dụ về directive :

var app = angular.module("test",[]);

app.directive("myDirective",function(){

  return {

      restrict: "EA",

      scope: true,

      link: function(scope,elem,attr){

          // code goes here ...

      }

  }

});

Ở ví dụ trên, mình đã tạo ra một directive bằng cách trả về một DDO từ function. Ở đây có nhiều thuộc tính của DDO, nhưng chúng ta sẽ tập trung vào thuộc tính scope, bởi vì giá trị của thuộc tính scope quyết định làm thế nào mà một scope thực tế được tạo ra và sử dụng ở bên trong một directive. Giá trị này có thể là “false”, “true”, hoăc “{}”. Và mình sẽ chỉ ra sự khác nhau giữa các giá trị ấy, nó ảnh hưởng gì đến behaviour của directive. Các kiểu khác nhau của directive scopes.

  1. Scope : false

Cùng xem một ví dụ khác, chúng ta sẽ tạo một directive đơn giản để render một thẻ div và một textbox để hiển thị và thay đổi “name”. Thuộc tính “name” lấy giá trị khởi tạo từ myController (scope cha của directive).

https://jsfiddle.net/cpnw83jb/

Nếu chúng ta thay đổi tên bên trong textbox thì bạn có thấy rằng tên ở bên trên header cũng thay đổi theo. Bởi vì không cung cấp scope ở DDO nên directive sẽ sử dụng chính scope của parrent. Do đó, bất cứ thay đổi nào mà chúng ta tạo ra ở bên trong directive cũng ảnh hưởng đến parent scope. Tương tự như thế, parent scope có một phương thức để đảo ngược name và được gọi đến khi chúng ta click vào header. Và nếu bạn click vào đó thì nó cũng đồng thời đảo ngược name ở bên trong directive.

  1. Scope : true

Bây giờ chúng ta sẽ để cho directive có thể get được scope của chính nó bằng cách set giá trị “true” cho thuộc tính scope ở DDO. Khi directive scope được set là “true”, AngularJS sẽ tạo ra một đối tượng scope mới và gán nó cho directive. Đối tượng scope mới được tạo này được kế thừa từ chính parent scope của nó (chính là controller scope)

Bạn có thấy khó hiểu? Sự khác biệt giữa scope: true và scope: false là gì?

Với scope được set bằng true, AngularJS sẽ tạo ra một scope mới bằng các kế thừa từ parent scope (thông thường là controller scope, nếu không thì sẽ là rootScope của ứng dụng). Bất kì thay đổi nào với scope mới này đều sẽ không ảnh hưởng tới parent scope.

Với scope được set bằng false, thì directive sẽ sử dụng chính scope của parent. Và đó là nguyên nhân khiến cho bất kỳ thay đổi nào ở directive đều ảnh hưởng tới parrent và ngược lại. Xem ví dụ sau để hiểu rõ hơn.

https://jsfiddle.net/yx2nzmxq/

Đầu tiên khi bạn click vào header thì có thể thấy được name ở cả controller và directive đều bị đảo ngược lại. Sau đó khi bạn thay đổi name ở bên trong textbox, thì parent scope không bị ảnh hưởng. Đồng thời khi bạn click vào header thì name ở bên trong directive cũng không bị đảo ngược lại nữa.

Ở phần này ng-model sẽ tạo ra một thuộc tính name mới khi mà giá trị của textbox bị thay đổi. Trước đó, thuộc tính name ở bên trong directive sẽ tham chiếu đến parent scope của nó (thông qua prototype chain).

  1. Scope : {}

Ở loại này directive sẽ lấy một scope độc lập. Chúng ta sẽ thiết lập thuộc tính scope ở DDO bằng một đối tượng mới, khi đối tượng này được truyền vào thuộc tính scope thì sẽ có một chút thay đổi. Lần này thì một scope mới được tạo ra cho directive, và nó không kế thừa từ parent scope. Đây là một scope mới và được biết đến như là “Isolated scope” bời vì nó hoàn toàn tách khỏi parent scope.inputnotification. Hãy sửa lại ví dụ ban đầu của chúng ta như sau. var app = angular.module("test",[]);

app.directive("myDirective",function(){

  return {

      restrict: "EA",

      scope: {},

      link: function(scope,elem,attr){

          // code goes here ...

      }

  }

 });

Mình gợi ý rằng bạn nên dùng cách này để thiết lập scope cho DDO trong lúc tạo custom directive. Bởi vì nó sẽ đảm bảo rằng scope của directive là độc lập, không ảnh hưởng tới các nơi khác ở bên trong ứng dụng. Và parent scope cũng sẽ không can thiệp vào bên trong directive scope.

Hãy xem ví dụ để hiểu rõ hơn.

https://jsfiddle.net/ovzax6sf/

Có thể thấy rằng ở đây chúng ta đã tạo ra một scope độc lập. Và việc giá trị khởi tạo giá trị cho thuộc tính name của parent scope là gì thì nó cũng không ảnh hưởng gì đến thuộc tính name của directive.

Đôi khi chúng ta cần phải truyền các giá trị từ parent scope vào bên trong directive thì làm thế nào?

Để truy cập được bất kì data nào của scope, chúng ta cần phải truyền nó tới directive. Điều này được thực hiện bàng cách thiết lập các thuộc tính ở trong đối tượng scope của DDO. Cùng xem ví dụ sau để hiểu rõ hơn.

https://jsfiddle.net/9c3mpvuq/

Để hiểu được thì chúng ta sẽ bắt đầu với đọan code javascript.

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

app.controller("MainCtrl", function( $scope ){
    $scope.name = "your_name";
    $scope.color = "#000000";
    $scope.reverseName = function(){
     $scope.name = $scope.name.split("").reverse().join("");
    };
    $scope.randomColor = function(){
        $scope.color = '#'+Math.floor(Math.random()*16777215).toString(16);
    };
});

app.directive("myDirective", function(){

    return {
        restrict: "EA",
        scope: {
            name: "@",
            color: "=",
            reverse: "&"
        },
        template: [
            "<div class='line'>",
            "Name : <strong>{{name}}</strong>;  Change name:<input type='text' ng-model='name' /><br/>",
            "</div><div class='line'>",
            "Color : <strong style='color:{{color}}'>{{color|uppercase}}</strong>;  Change color:<input type='text' ng-model='color' /><br/></div>",
            "<br/><input type='button' ng-click='reverse()' value='Reverse Name'/>"
        ].join("")
    };
});

Ở đây controller MainCtrl đã tạo ra parent scope với các thuộc tính và các phương thức.

name = "your_name"
color = "#000000"
reverseName = function for reversing the name
randomColor = function for generating random color code

Tương tự như thế thì chúng ta tạo ra một directive với scope độc lập. Nhưng chú ý rằng đối tượng scope có một vài thuộc tính như sau

scope: {
            name: "@",
            color: "=",
            reverse: "&"
        }

Nhìn vào directive template và chúng ta có thể thấy rằng thuộc tính của scope được sử dụng ở đây. Hầu hết các template của directive và link funtion đều dùng các thuộc tính của scope. Và các behaviour của các thuộc tính này lại phụ thuộc vào giá trị của chúng, được biết đến như là prefixes. Các prefixes này được sử dụng để truyền thuộc tính và phương thước của parent scope tới directive scope. Có 3 kiểu prefixes mà AngularJS cung cấp đó là

  1. "@" ( Text binding / one-way binding )
  2. "=" ( Direct model binding / two-way binding )
  3. "&" ( Behaviour binding / Method binding )

Tất cả các prefixes này nhận dữ liệu từ thuộc tính của directive element. Bạn có thể thấy ở trong code HTML.

<div class="directive" my-directive
    name="{{name}}"
    color="color"
    reverse="reverseName()"
></div>

Khi directive có một prefix ở trong thuộc tính scope, nó sẽ tìm kiếm một thuộc tính (với cùng tên) ở phần tử html của directive. Bài viết về scope của directive của mình đến đây là hết. Hẹn gặp lại ở bài viết sau.

0