-1

Scope of the Directive

I. Scope in Angularjs

  • Không giống như những MVC frameworks khác, AngularJS không có các class cụ thể hay các function để tạo các model objects. Thay vào đó AngularJS extend từ các đối tượng javascript thuần. Những objects này được gọi là scope trong AngularJS. Nó có nhiệm vụ kết nối, truyền data giữa view với các thành phần khác như directives, controller, services bên trong ứng dụng AngularJS
  • Bất cứ khi nào ứng dụng AngularJS được load, một rootScope object sẽ được tạo, mỗi scope được tạo bên trong controller, directives, hay services sẽ kế thừa rootScope này.

II. Scope bên trong directives

  • Tất cả các directives đều có scope. Directive sử dụng scope cho việc truy cập data/method bên trong template và link function. Theo mặc định thì directives sẽ không tự tạo scope cho riêng chúng mà sử dụng luôn parent scope (thông thường là scope của controller)
  • AngularJS cũng cho phép thay đổi scope mặc định của directives bằng cách truyền một DDO object
    var app = angular.module("test",[]);
    app.directive("myDirective",function(){
      return {
          restrict: "EA",
          scope: true,
          link: function(scope,elem,attr){
              // code goes here ...
          }
      }
     });
  • Trong ví dụ trên directive sẽ trả về một DDO từ function trong đó bao gồm scope property. Gía trị của scope sẽ quyết định việc scope thực sự được tạo và sử dụng bên trong directive như thế nào. Những giá trị của scope có thể là false, ``truehoặc là{}`

II.1. Các kiểu directive scope

II.1.1 Scope: false ( directive sẽ sử dụng parent scope)

  • Xét ví dụ sau: Tạo một directive để render một div và một textbox có thể hiển thị và thay đổi giá trị của name. Thuộc tính name sẽ được khởi tạo giá trị từ ctr1 scope tức là từ scope cha của directive.
    <div ng-app="test">

        <div ng-controller="Ctrl1">
            <h2 ng-click="reverseName()">Hey {{name}}, Click me to reverse your name</h2>
            <div my-directive class='directive'></div>
        </div>
    </div>

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

    app.controller("Ctrl1",function($scope){
        $scope.name = "Harry";
        $scope.reverseName = function(){
            $scope.name = $scope.name.split('').reverse().join('');
        };
    });
    app.directive("myDirective", function(){
        return {
            restrict: "EA",
            scope: false,
            template: "<div>Your name is : {{name}}</div>"+
            "Change your name : <input type='text' ng-model='name' />"
        };
    });
```

Link https://jsfiddle.net/shidhincr/eyNYw/4/?utm_source=website&utm_medium=embed&utm_campaign=eyNYw

- Nếu ta thay đổi name bên trong textbox, header cũng bị thay đổi theo. Bởi vì không có scope trong DDO, Directive sử dụng parent scope. Do vậy nên bất cứ sự thay đổi nào bên trong directive nó cũng thay đổi parent scope.

### II.1.2 Scope: true. (Directive sẽ tạo một scope mới)

- Để một directive có scope của riêng nó ta phải gán giá trị `true` cho scope bên trong DDO, lúc này AngularJS sẽ tạo ra một scope object mới và gán lại cho directive. Scope mới này có thể được coi là bản sao và được kế thừa từ parent scope ( controller scope )

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

    app.controller("Ctrl1",function($scope){
        $scope.name = "Harry";
        $scope.reverseName = function(){
            $scope.name = $scope.name.split('').reverse().join('');
        };
    });
    app.directive("myDirective", function(){
        return {
            restrict: "EA",
            scope: true,
            template: "<div>Your name is : {{name}}</div>"+
            "Change your name : <input type='text' ng-model='name' />"
        };
    });

```

- Bạn vào jsfidle để xem kết quả
https://jsfiddle.net/shidhincr/q3kex/3/?utm_source=website&utm_medium=embed&utm_campaign=q3kex
Click vào header, name sẽ được đảo ngược bên trong controller `Ctrl1` và directive. Sau đó bạn thay đổi name bên trong textbox, parent scope không bị ảnh hưởng header text không bị thay đổi.

**Điểm khác biệt giữa `scope: true``scope: false` **
-  Khi scope được set thành `true` AngularJS sẽ tạo một cope mới bằng cách kế thừa `parent scope` ( thông thường là controller scope, ngoài ra là `rootScope`). Bất cứ sự thay đổi nào của new scope trong directive sẽ không ảnh hưởng tới `parent scope` nhưng ngược lại thì không bất cứ sự thay đổi nào của `parent scope` cũng kéo theo sự thay đổi của scope mới trong directive. Bất cứ sự thay đổi nào bên trong `Ctrl1` (parent scope) sẽ kéo theo sự thay đổi của directive scope.
- Khi scope được set thành `false`, Controller `Ctrl1` và directive dùng chung một scope object do đó bất cứ sự thay đổi nào 1 trong 2 scope cũng sẽ kéo theo sự thay đổi của scope còn lại. hay nói cách khác chúng đồng bộ.

### II.1.3 Scope: {}

- Gán một `Object literal` cho `scope` bên trong DDO, Angular sẽ tạo ra một scope mới cho directive nhưng scope này không kế thừa từ parent scope. Nó là scope mới hoàn toàn và được gọi là `Isolate scope`.

```javascript
    var app = angular.module("test",[]);
    app.directive("myDirective",function(){
      return {
          restrict: "EA",
          scope: {},
          link: function(scope,elem,attr){
              // code goes here ...
          }
      }
     });
```

- Đây là kiểu viết được khuyến cáo khi tạo một custom directive bởi vì nó sẽ đảm bảo directive có thể dùng ở mọi nơi bên trong ứng dụng. Parent scope sẽ không thể can thiệp vào bên trong directive scope.
Ngoài ra AngularJS cho phép `Isolate scope` giao tiếp với parent scope thông một vài ký tự đặc biệt. Bởi vì trong một vài tình huống directive scope cần trao đổi dữ liệu với parent scope.

## II.2 Isolated Scope

- Trước tiên hay xem code trên jsfidle https://jsfiddle.net/shidhincr/q3kex/4/?utm_source=website&utm_medium=embed&utm_campaign=q3kex

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

    app.controller("Ctrl1",function($scope){
        $scope.name = "Harry";
        $scope.reverseName = function(){
            $scope.name = $scope.name.split('').reverse().join('');
        };
    });
    app.directive("myDirective", function(){
        return {
            restrict: "EA",
            scope: {},
            template: "<div>Your name is : {{name}}</div>"+
            "Change your name : <input type='text' ng-model='name'/>"
        };
    });
```

- Ta vừa tạo ra một directive mới một `Isolate scope`, Chú ý rằng thậm chí parent scope có tên “Harry” hay là gì đi nữa, thì textbox bên trong directive cũng là rỗng. Đó là vì Isolate scope không hiểu parent scope.
Nhưng chúng ta có thể truyền giá trị từ parent scope sang directive scope, để access vào parent scope chúng ta cần set properties cho scope object bên trong DDO. Có thể nói những properties này như là một cầu nối để directive có thể giao tiếp với bên ngoài. Và một điều quan trọng nữa là những properties này phải được set như là những attributes của html direcctive.

https://jsfiddle.net/shidhincr/pJLT8/10/?utm_source=website&utm_medium=embed&utm_campaign=pJLT8

- Hãy xem đoạn code sau

```javascript
     var app = angular.module("app", []);
     app.controller("MainCtrl", function( $scope ){
        $scope.name = "Harry";
        $scope.color = "#333333";
        $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></strong>;  Change name:<input type='text' ng-model='name' /><br/>",
                "</div><div class='line'>",
                "Color : <strong style='color:'></strong>;  Change color:<input type='text' ng-model='color' /><br/></div>",
                "<br/><input type='button' ng-click='reverse()' value='Reverse Name'/>"
            ].join("")
        };
    });

```

- Controller `MainCtrl` tạo ra `parent scope`, `parent scope` này sẽ có các thuộc tính sau:

```
    name = "Harry"
    color =  "#333333"
    reverseName = function for reversing the name
    randomColor = function for generating random color code
```

- Tạo ra một Isolate scope bên trong directive bằng một `object literal` bên trong `DDO`, và có các thuộc tính sau:

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

```

- Có thể thấy scope properties được sử dụng bên trong directive template, Hầu hết directive template và link function đều dùng scope properties. Và hành vi của những properties này phụ thuộc vào giá trị của nó gọi là `Prefixes`. Những `Prefixes` này được sử dụng để bind các method, properties của parent scope vào directive scope.
- Có 3 kiểu Prefixes trong AngularJS.

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

- Tất cả các directives sẽ nhận data từ thuộc tính của directive element.

- Hãy xem đoạn HTML sau:

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

- Khi một directive gặp một prefix của scope property, nó sẽ tìm kiếm một attribute có cùng tên property ở directive html. Thế nhưng chúng ta có thể đưa ra các cách mapping khác nhau giữa property và attributes bằng cách đặt attribute name đằng sau prefix.

```
    scope : {
      name: "@"
    }
```

Ở ví dụ trên sẽ map với attribute `name` trên directive. Tiếp tục xem đoạn code sau

```
    scope : {
      name: "Name"
    }
```

- Tại thời điểm này name property sẽ tìm kiếm một attribute `parent-name` bên trong html element để lấy giá trị của nó. Có thể hiểu đơn giản là bất kỳ chuỗi string nào sau Prefixes sẽ match với attribute name của HTML.

## II.3 Prefixes

### II.3.1 @

- Prefix `@` là kiểu one-way binding giữa directive scope và parent scope, nghĩa là bất cứ sự thay đổi nào của parent scope cũng kéo theo sự thay đổi của directive scope. Nhưng không có chiều ngược lại.
- Một chú ý quan trọng đó là để @ làm việc được chúng ta cần để attribute value vào bên trong `{{}}`.
- Prefix này rất có ích khi directive cần được khởi tạo giá trị từ parent scope.

### II.3.2. =

- Nó là kiểu two-way binding giữa parent scope và directive scope. Nghĩa là bất sự thay đổi nào của 1 trong 2 scope cũng ảnh hưởng tới scope còn lại.
- Chú ý là attribute value phải là model name, điều đó có nghĩa là bạn không thể để attribute value bên trong expression như @ prefix.

### II.3.3. &

- Nó là method binding, được sử dụng để bind tất cả các phương thức từ parent scope sang directive.
- Nó hữu ích trong trường hợp directive cần phải thực thi callback của parent scope.

# Nguồn tham khảo

https://www.undefinednull.com/2014/02/11/mastering-the-scope-of-a-directive-in-angularjs/

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í