Scope là thành phần nền tảng trong AngularJS, được sử dụng khắp nơi trong các ứng dụng được phát triển trên nền tảng AngularJS. Chính vì tầm quan trọng của Scope như vậy, chúng ta cần hiểu rõ cơ cấu và cơ chế làm việc của Scope.
Nội dung trình bày
Scope trong AngluarJS là gì?
Trong AngularJS, Scope là các đối tượng chứa hàm và dữ liệu, do Controller khai báo và khởi tạo, được sử dụng và hiển thị trong View. Chúng ta có thể hình dung Scope như là thành phần mô hình dữ liệu (Model) gắn kết Controller và View.
Các đặc tính của Scope
Để hiểu rõ và đầy đủ về Scope, chúng ta sẽ điểm qua các đặc trưng cũng như vai trò của Scope trong ứng dụng AngularJS trong danh sách dưới đây:
- Scope là nơi để định nghĩa và lưu giữ Model của ứng dụng
- Scope đóng vai trò làm ngữ cảnh của expression
- Scope được trang bị cơ chế móc nối trực tiếp (live binding) với View: chúng ta có thể dựa vào Scope để cập nhật Data Model ngay tức thì khi View thay đổi, cũng như dựa vào Scope để cập nhật View mỗi khi có sự thay đổi trong Data Model. Nhờ đặc điểm này mà chúng ta có thể đảm bảo được sự nhất quán của trạng thái ứng dụng.
- Scope cung cấp cơ chế theo dõi (observe) sự thay đổi của Model thông qua hàm $watch(), cho phép đăng ký các bộ xử lý sự kiện mỗi khi có thay đổi.
- Scope cung cấp cơ chế lan truyền (propagate) những thay đổi của Model, do bên ngoài phạm vi AngularJS (controllers, services, AngularJS event handlers) tác động, lên View thông qua hàm $apply().
- Scope được tổ chức lồng nhau để có thể cô lập hoặc chia sẻ các chức năng và thuộc tính của các thành phần trong ứng dụng.
Scope là nơi để định nghĩa và lưu giữ trường dữ liệu
Chức năng đầu tiên của Scope chính là nơi lưu trữ thông tin sẵn sàng để hiển thị. Mỗi Controller khi được gắn kết vào HTML (bằng khai báo ng-controller) đều tạo riêng một đối tượng $scope chung cho nó và khối HTML tương ứng. Trong ví dụ dưới đây, ta có thể định nghĩa một Controller và khởi tạo các trường dữ liệu lưu trữ trong đối tượng $scope của nó:
Scope đóng vai trò là ngữ cảnh của Expression
Các biểu thức (Expression) được dùng để “điền” giá trị dữ liệu trong Scope lên View (HTML). Mặc dù expression được viết ngắn gọn, sử dụng tên của các trường dữ liệu, nhưng AngularJS sẽ hiểu các trường dữ liệu này nằm trong Scope gần nhất của biểu thức.
Scope gắn kết dữ liệu 2 chiều với View
Dữ liệu trong Scope gắn kết 2 chiều với View, hiểu theo nghĩa mỗi khi chỉnh sửa thông tin trên View thì dữ liệu tương ứng bên trong Scope thay đổi theo và ngược lại mỗi khi dữ liệu trong Scope thay đổi cũng sẽ làm thay đổi thông tin trên View.
Trong ví dụ trên, nếu chúng ta chỉnh sửa trường dữ liệu “fullname” trên giao diện thì dữ liệu trong Scope cũng được cập nhật, dẫn đến tiêu đề cũng tương ứng thay đổi theo.
Scope cung cấp cơ chế theo dõi thay đổi của trường dữ liệu
Scope được trang bị hàm $watch() cho phép đăng ký một hàm theo dõi sự thay đổi giá trị của một trường dữ liệu trong Scope. Mỗi khi trường dữ liệu này thay đổi giá trị, hàm theo dõi sẽ được kích hoạt. Ví dụ sau đây minh họa điều đó:
Với các Observing directive (chẳng hạn như biểu thức nằm giữa *, *ng-model, …) đều được AngularJS tự động sử dụng $watch() đăng ký hàm theo dõi lên các trường dữ liệu liên quan trong Scope.
Scope cung cấp cơ chế lan truyền thay đổi của trường dữ liệu
Cơ chế lan truyền thay đổi được thực hiện bởi hai hàm $digest() và $apply() định nghĩa trong Scope.
Mỗi khi được gọi, hàm $digest() sẽ duyệt và thực thi tất cả các bộ theo dõi ($watch) được khai báo trong Scope đó, cũng như của các Scope con của nó. Bên trong các thành phần AngularJS, hàm $digest() được gọi ở những chỗ AngularJS thấy cần thiết kích hoạt các bộ theo dõi để cập nhật thay đổi.
Hàm $apply() cho phép người lập trình viết thêm một số lệnh trước khi nó tự động gọi hàm $digest(). Mã lệnh viết thêm đuược cho dưới dạng một hàm là tham số của $apply(). Mục đích của $apply() là tạo sự thuận tiện để người lập trình viết mã lệnh thực thi đồng thời đảm bảo (rằng không bị quên) gọi hàm $digest() sau khi thực thi xong các lệnh đó.
Ví dụ dưới đây minh họa việc sử dụng hàm $digest() và $apply():
Sự kế thừa của AngularJS Scope
Trải nghiệm về sự kế thừa của Scope
Để có cảm nhận và trải nghiệm về sự kế thừa lồng nhau của Scope, trước tiên chúng ta xem xét ví dụ minh họa sau. Giả sử chúng ta đang phát triển một ứng dụng thương mại điện tử, cần xây dựng một giao diện nhập liệu cho sản phẩm là thiết bị di động. Dễ thấy là, để nhập liệu một cách nhanh chóng nhiều sản phẩm có thuộc tính giống nhau (kích thước, màu sắc, các đặc điểm kỹ thuật, thậm chí một phần tên gọi), chúng ta có thể tạo một form nhập liệu chung cho nhiều sản phẩm, sau đó chỉnh sửa những thông tin khác biệt cho từng sản phẩm.
Mô hình kế thừa theo mẫu hình
Mô hình kế thừa theo mẫu hình (Prototypal Inheritance) không xa lạ gì với lập trình viên Javascript, tuy nhiên lại khá mới mẻ với những lập trình viên chỉ thành thạo những ngôn ngữ phổ dụng Java, C#, PHP,… Tại sao lại như vậy? Vì lập trình hướng đối tượng trong các ngôn ngữ Java, C#, PHP thực hiện thông qua mô hình Class: các đối tượng được mô tả dưới dạng Class, sau đó tạo đối tượng là thể hiện (instantiate) của Class đó. Còn trong Javascript thì không cần tạo Class. Để tạo nhiều đối tượng có cấu trúc giống nhau, người ta tạo một đối tượng và dùng nó làm mẫu hình (Prototype), các đối tượng khác được tạo ra bằng cách sao chép đối tượng mẫu này.
// Tạo đối tượng dùng làm mẫu hình (Prototype)
var myPrototype = {a: 100};
// Hàm cấu tử (là hàm dùng với toán tử new để tạo đối tượng)
var myConstructor = function(name) {
this.name = name;
}
// Khai báo mẫu hình cho hàm cấu tử
myConstructor.prototype = myPrototype;
// Tạo đối tượng từ hàm cấu tử
var myObject = new myConstructor("Javascript");
alert(myObject.name);
alert(myObject.a); // hiển thị a = 100
myPrototype.a = 102;
alert(myObject.a); // hiển thị a = 102
myObject.a = 100;
myPrototype.a = 105;
alert(myObject.a); // hiển thị a = 100