Scope và rootScope trong AngularJS – Web888 chia sẻ kiến thức lập trình, kinh doanh, mmo

Ở các bài trước, chúng ta đã tạm hiểu cấu trúc MVC của AngularJS:

  • View: dữ liệu hiển thị HTML
  • Model: dữ liệu làm việc với view hiện tại
  • Controller: luồng logic được viết bằng angularJS functions, sử dụng để thêm, sửa , xóa dữ liệu

$scope là một object (đối tượng) có nhiệm vụ giao tiếp dữ liệu giữa controller và view của ứng dụng, chúng ta có thể hiểu là cầu nối, khi biến $scope thay đổi, dữ liệu ở controller và view cùng được cập nhật đồng bộ . Nó sẽ thực hiện dưới dạng biểu thức, nghĩa là ở model sẽ được khai báo đúng với quy cách thì đối tượng scope sẽ truyền hành động (function) hoặc dữ liệu tương ứng và ta có thể truyền các sự kiện thông qua đối tượng này.

Scopes cung cấp các biểu thức giống như các template engine hiện nay, ví dụ để hiển thị username thì ta sẽ khai báo là {{username}} và ở controller chúng ta chỉ việc gán $scope.username = ‘toanngo92’ thì đối tượng này sẽ lấy key có tên là username gán vào view {{username}}.

Phạm vi ảnh hưởng của $scope

Trong một ứng dụng AngularJS thì ta có thể có nhiều Controller, nhiều $scope khác nhau. Quay trở lại ví dụ dưới đây:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Example Controller</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>
    <div ng-app="examController">
        <div ng-controller="headerController">
            {{name}} - {{data}}
        </div>
        <div ng-controller="mainController">
            {{name}} - {{data}}
        </div>
        <div ng-controller="footerController">
            {{name}} - {{data}}
        </div>
    </div>
    <script>
        var app = angular.module('examController', []);
        app.controller('headerController', function($scope){
            $scope.name = "This is header";
        });
        app.controller('mainController', function($scope){
            $scope.name = "This is main";
            $scope.data = "main data";
        });
        app.controller('footerController', function($scope){
            $scope.name = "This is footer";
        });
    </script>
</body>
</html>

Chúng ta thấy trong các view liên kết tới controller tương ứng đều giá trị {{data}} khác nhau tuy nhiên mỗi một controller, thuộc tính data trong biến ở mỗi controller khác nhau. Điều này chứng minh cho việc phạm vi của biến $scope chỉ nằm trong controller tương ứng.

$rootScope và cấu trúc phân tầng của $scope

Ví dụ số 1:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="angular.min.js"></script>
	<!-- <script src="angular-route.min.js"></script> -->
</head>
<body ng-app="toanApp">
    <div >Data rootscope: {{name}}</div>
    <div ng-controller="indexController">
        <input ng-model="name" value=""/>
        <p> Data scope {{name}}</p>
        <button ng-click="changename()" >click</button>
    </div>
    <script>
        var app = angular.module("toanApp",[]);
        app.run(function($rootScope){
            $rootScope.name = "root scope";
        });
        app.controller(
            "indexController",
            function($scope){
               // console.log(languages);
                $scope.name = "controller scope";
                $scope.changename = function(e){
                    $scope.name = "test";
                }
            }
        );
    </script>
</body>
</html>

Kết quả khi in ra, và tương tác với input

Với ví dụ trên, chúng ta thấy mình có sử dụng một div và gọi model name ra view, tuy nhiên model này không thuộc controller nào, chúng ta hiểu ở dưới tầng script, thông qua phương thức app.run(), chúng ta đã khởi tạo một model toàn cục tên là name, nên mặc dù không nằm trong controller nào, chúng ta vẫn có thể biểu diễn ra biến name. còn đối với model name ở cấu trúc div bên trong indexController, chúng ta thấy khi cập nhật dữ liệu, model ở controller thay đổi nhưng không liên quan gì tới model bên ngooài, điều đó có ý nghĩa, 2 model name nằm trong $scope$rootScope mặc dù cùng tên nhưng bản chất chúng không liên quan tới nhau

Ví dụ số 2:

bây giờ ta sẽ đổi một chút cú pháp của đoạn mã AngularJS như sau:

Trong đoạn ví dụ này sự khác biệt chính là ở đoạn mã JS Controller thứ nhất có thêm tham số $rootScope, và ở đoạn Controller thứ hai thì không xử lý gì cả. Chạy lên các bạn thấy giao diện như sau:

Như vậy rõ ràng đoạn code trên mình không truyền giá trị cho $scope ở cả 2 controller mà bên view vẫn có? Đó là vì biến $rootScope. Điều này có nghĩa là khi ứng dụng được chạy thì sẽ có một $rootScope được tự động tạo, $rootScope là bậc cao nhất nên sẽ bao quát hết các $scope bên trong nó, điêu này không giống với $scope là chỉ ảnh hưởng trong phạm vi của controller.

Xét ví dụ phía dưới:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Example Controller</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>
    <div ng-app="examRootScope">
        <div ng-controller="headerController">
            {{name}} - {{data}} {{data_global}}
        </div>
        <div ng-controller="mainController">
            {{name}} - {{data}}
        </div>
        <div ng-controller="footerController">
            {{name}} - {{data}}
        </div>
    </div>
    <script>
        var app = angular.module('examRootScope', []);
        app.controller('headerController', function($scope,$rootScope){
            $scope.name = "This is header";
            $scope.data= "header data scope";
            $rootScope.data = "header data root scope";
        });
        app.controller('mainController', function($scope){
            $scope.name = "This is main";
            $scope.data = "main data";
        });
        app.controller('footerController', function($scope,$rootScope){
            $scope.name = "This is footer";
            $scope.data = "footer data scope";
            $rootScope.data_global = "thuoc tinh dung chung";
        });
    </script>
</body>
</html>

Kết quả in ra màn hình:

Bổ sung thêm một số thông tin ở ví dụ phía trên, có 2 ý cần quan tâm:

  • Ở headerController, 2 thuộc tính $scope.data và $rootScope.data được đặt tên giống nhau (data), tuy nhiên khi in ra view bằng biểu thức, giá trị sẽ được lấy từ thuộc tính data của bến $scope, chứng minh nếu trong trường hợp model có tên giống nhau, $scope sẽ đưược ưu tiên so với $rootScope
  • Ở footerController, có một thuộc tính có tên data_global do mình tự đặt, tuy nhiên ở tầng view liên kết với headerController, mình lại cố tình gọi model data_global này ra, nhưng views vẫn nhận diện được, điều này chứng minh dữ liệu trong $rootScope có tính toàn cục ( có thể sử dụng để dùng chung hoặc truyền dữ liệu giữa các controller)

Nested Scope (scope lồng nhau)

Trong angularJS, chúng ta có thêm khái niệm các controller lồng nhau, tương tự các scope cũng lồng nhau, với tình huống này cần rất cẩn thận vì sẽ có nhiều tình huống không phân biệt được đang sử dụng scope nào. Xét ví dụ phía dưới:

<!DOCTYPE html>
<html lang="en" ng-app="myApp">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>

<body ng-controller="parentController">
    What controller am I? {{parentVariable}}
    <div ng-controller="childController">
        What controller am I? {{childVariable}} {{parentVariable}}
        <button ng-click="childFunction()"> Click me to override! </button>
    </div>
</body>
<script>
    var app = angular.module('myApp', []);
    app.controller('parentController', function ($scope) {
        $scope.parentVariable = "I'm the parent";
    });

    app.controller('childController', function ($scope) {
        $scope.childVariable = "I'm the child";
        $scope.childFunction = function () {
            $scope.parentVariable = "Parent changed!";
            console.log($scope);
        };
    });
</script>
</html>

Kết quả khi chạy thử và bấm nút override:

Vấn đề ở đây, khi log biến $scope ra, dữ liệu parentVariable đã thay đổi, tuy nhiên parentVariable ở parentController lại không được cập nhật, vấn đề này sẽ thực sự gây khó hiểu và bất tiện trong quá trình coding, nên lời khuyên cho bạn là nên đặt tên biến rõ ràng, nếu có phải sử dụng nested controller thì nên tìm hiểu cấu trúc thật kỹ để tránh phát sinh lỗi không mong muốn.

Ngoài ra, nếu nói về scope thì thực sự sẽ có rất nhiều vấn đề, tuy nhiên trong khuôn khổ tiếp cận, những khái niệm này là những thứ chúng ta cần nắm bắt trước khi đi vào làm một ứng dụng thực tế với angularjs.