Làm cách nào để sử dụng $ scope. $ Watch và $ scope. $ Áp dụng trong AngularJS?


1088

Tôi không hiểu cách sử dụng $scope.$watch$scope.$apply. Các tài liệu chính thức không hữu ích.

Những gì tôi không hiểu cụ thể:

  • Chúng có được kết nối với DOM không?
  • Làm cách nào để cập nhật các thay đổi DOM cho mô hình?
  • Điểm kết nối giữa chúng là gì?

Tôi đã thử hướng dẫn này , nhưng nó cần sự hiểu biết $watch$applycho phép.

Làm gì $apply$watchlàm gì, và làm thế nào để tôi sử dụng chúng một cách thích hợp?

Câu trả lời:


1737

Bạn cần lưu ý về cách AngularJS hoạt động để hiểu nó.

Chu kỳ tiêu hóa và phạm vi $

Đầu tiên và quan trọng nhất, AngularJS định nghĩa một khái niệm về cái gọi là chu trình tiêu hóa . Chu trình này có thể được coi là một vòng lặp, trong đó AngularJS kiểm tra nếu có bất kỳ thay đổi nào đối với tất cả các biến được theo dõi bởi tất cả các $scopes. Vì vậy, nếu bạn đã $scope.myVarxác định trong bộ điều khiển của mình và biến này được đánh dấu để được theo dõi , thì bạn đang ngầm nói với AngularJS để theo dõi các thay đổi trên myVartrong mỗi lần lặp của vòng lặp.

Một câu hỏi tiếp theo tự nhiên sẽ là: Có phải mọi thứ đều gắn liền với $scopeviệc được theo dõi? May mắn thay, không. Nếu bạn sẽ theo dõi các thay đổi đối với mọi đối tượng trong bạn $scope, thì nhanh chóng một vòng lặp tiêu hóa sẽ mất nhiều thời gian để đánh giá và bạn sẽ nhanh chóng gặp phải các vấn đề về hiệu suất. Đó là lý do tại sao nhóm AngularJS đã cho chúng tôi hai cách khai báo một số $scopebiến là được theo dõi (đọc bên dưới).

$ watch giúp lắng nghe thay đổi phạm vi $

Có hai cách khai báo một $scopebiến là được theo dõi.

  1. Bằng cách sử dụng nó trong mẫu của bạn thông qua biểu thức <span>{{myVar}}</span>
  2. Bằng cách thêm nó thủ công thông qua $watchdịch vụ

Quảng cáo 1) Đây là tình huống phổ biến nhất và tôi chắc chắn bạn đã từng thấy nó trước đây, nhưng bạn không biết rằng điều này đã tạo ra một chiếc đồng hồ ở chế độ nền. Vâng, nó đã có! Sử dụng các chỉ thị AngularJS (chẳng hạn như ng-repeat) cũng có thể tạo ra đồng hồ ngầm.

Quảng cáo 2) Đây là cách bạn tạo đồng hồ của riêng mình . $watchdịch vụ giúp bạn chạy một số mã khi một số giá trị được đính kèm $scopeđã thay đổi. Nó hiếm khi được sử dụng, nhưng đôi khi là hữu ích. Ví dụ: nếu bạn muốn chạy một số mã mỗi lần thay đổi 'myVar', bạn có thể thực hiện các thao tác sau:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$ áp dụng cho phép tích hợp các thay đổi với chu trình phân loại

Bạn có thể nghĩ về $applychức năng như một cơ chế tích hợp . Bạn thấy đấy, mỗi khi bạn thay đổi một số biến được xem$scope trực tiếp gắn vào đối tượng, AngularJS sẽ biết rằng sự thay đổi đã xảy ra. Điều này là do AngularJS đã biết để theo dõi những thay đổi đó. Vì vậy, nếu nó xảy ra trong mã được quản lý bởi khung, chu trình phân loại sẽ tiếp tục.

Tuy nhiên, đôi khi bạn muốn thay đổi một số giá trị bên ngoài thế giới AngularJS và xem các thay đổi lan truyền bình thường. Hãy xem xét điều này - bạn có một $scope.myVargiá trị sẽ được sửa đổi trong $.ajax()trình xử lý của jQuery . Điều này sẽ xảy ra tại một số điểm trong tương lai. AngularJS không thể chờ đợi điều này xảy ra, vì nó đã không được hướng dẫn để chờ đợi trên jQuery.

Để giải quyết điều này, $applyđã được giới thiệu. Nó cho phép bạn bắt đầu chu trình tiêu hóa một cách rõ ràng. Tuy nhiên, bạn chỉ nên sử dụng điều này để di chuyển một số dữ liệu sang AngularJS (tích hợp với các khung công tác khác), nhưng không bao giờ sử dụng phương pháp này kết hợp với mã AngularJS thông thường, vì AngularJS sẽ gặp lỗi sau đó.

Làm thế nào là tất cả những điều này liên quan đến DOM?

Vâng, bạn thực sự nên làm theo hướng dẫn một lần nữa, bây giờ bạn biết tất cả điều này. Chu trình phân loại sẽ đảm bảo rằng giao diện người dùng và mã JavaScript được đồng bộ hóa, bằng cách đánh giá mọi người theo dõi được đính kèm với tất cả $scopemiễn là không có gì thay đổi. Nếu không có nhiều thay đổi xảy ra trong vòng lặp digest, thì nó được coi là kết thúc.

Bạn có thể đính kèm các đối tượng vào $scopeđối tượng một cách rõ ràng trong Bộ điều khiển hoặc bằng cách khai báo chúng {{expression}}dưới dạng trực tiếp trong dạng xem.

Tôi hy vọng điều đó sẽ giúp làm rõ một số kiến ​​thức cơ bản về tất cả điều này.

Đọc thêm:


57
"Kiểm tra góc nếu có bất kỳ thay đổi nào đối với tất cả các biến được gắn vào tất cả các phạm vi $" - Tôi không nghĩ điều đó hoàn toàn đúng. Tôi tin rằng chỉ Angular (bẩn) kiểm tra các thuộc tính $ scope đã thiết lập đồng hồ $ (lưu ý rằng việc sử dụng {{}} trong chế độ xem sẽ tự động tạo đồng hồ $). Xem thêm phần "Phạm vi $ Xem các cân nhắc về hiệu suất" trên trang Phạm vi .
Mark Rajcok

5
Đó có thể là trường hợp. Tôi sẽ cố gắng tìm thời gian để đọc thêm về nó và chỉnh sửa câu trả lời của tôi.
ukaszBachman

15
@MarkRajcok, bạn đã đúng. Tôi đã thay đổi câu trả lời của mình và chỉ ra một bài viết chỉ ra cách thức thực hiện.
ŁukaszBachman

3
Còn việc sử dụng cái này thì sao? (Phương pháp "Điều khiển như")
Leandro

2
Sử dụng "Kiểm soát như" sẽ không có tác động đến thông tin ở trên. Sử dụng this.myVar đặt myVar trên phạm vi.
Marcus Rådell

161

Trong AngularJS, chúng tôi cập nhật các mô hình của chúng tôi và các chế độ xem / mẫu của chúng tôi cập nhật DOM "tự động" (thông qua các chỉ thị tích hợp hoặc tùy chỉnh).

$ áp dụng và $ watch, cả hai đều là phương thức Phạm vi, không liên quan đến DOM.

Các khái niệm trang (phần "Runtime") có một lời giải thích khá tốt của $ tiêu hóa vòng lặp, $ áp dụng, hàng đợi $ evalAsync và danh sách $ đồng hồ. Đây là hình ảnh đi kèm với văn bản:

$ vòng lặp tiêu hóa

Bất kỳ mã nào có quyền truy cập vào một phạm vi - thông thường các bộ điều khiển và chỉ thị (chức năng liên kết và / hoặc bộ điều khiển của chúng) - có thể thiết lập một " watchExpression " mà AngularJS sẽ đánh giá theo phạm vi đó. Đánh giá này xảy ra bất cứ khi nào AngularJS đi vào vòng lặp $ digest của nó (đặc biệt là vòng lặp "danh sách theo dõi $"). Bạn có thể xem các thuộc tính phạm vi riêng lẻ, bạn có thể xác định một chức năng để xem hai thuộc tính cùng nhau, bạn có thể xem độ dài của một mảng, v.v.

Khi mọi thứ xảy ra "bên trong AngularJS" - ví dụ: bạn nhập vào hộp văn bản có bật cơ sở dữ liệu hai chiều AngularJS (ví dụ: sử dụng mô hình ng), một cuộc gọi lại $ http, v.v. - $ áp dụng đã được gọi, vì vậy chúng tôi đã gọi 'Bên trong hình chữ nhật "AngularJS" trong hình trên. Tất cả watchExpressions sẽ được đánh giá (có thể hơn một lần - cho đến khi không phát hiện thêm thay đổi nào).

Khi mọi thứ xảy ra "bên ngoài AngularJS" - ví dụ: bạn đã sử dụng bind () trong một lệnh và sau đó sự kiện đó kích hoạt, dẫn đến cuộc gọi lại của bạn được gọi hoặc một số cuộc gọi lại đã đăng ký jQuery - chúng tôi vẫn ở trong hình chữ nhật "Bản địa". Nếu mã gọi lại sửa đổi bất cứ điều gì mà bất kỳ đồng hồ $ nào đang xem, hãy gọi $ áp dụng để vào hình chữ nhật AngularJS, khiến vòng lặp $ digest chạy, và do đó AngularJS sẽ nhận thấy sự thay đổi và thực hiện phép thuật của nó.


5
Tôi hiểu ý tưởng, điều tôi không hiểu là làm thế nào dữ liệu thực sự được chuyển. Tôi có một mô hình là một đối tượng có nhiều dữ liệu, tôi sử dụng một số mô hình đó để thao tác DOM. sau đó một số được thay đổi. Làm cách nào để đặt dữ liệu đã thay đổi vào đúng vị trí trong mô hình? Trong ví dụ tôi đã sử dụng, anh ta thực hiện các thao tác và cuối cùng chỉ đơn giản là sử dụng scope.$apply(scope.model), tôi không hiểu dữ liệu nào được truyền và làm thế nào nó được chuyển đến đúng nơi trong mô hình?
ilyo

6
Không có chuyển dữ liệu ma thuật đang diễn ra. Thông thường với các ứng dụng Angular, bạn nên thay đổi các mô hình Angular, sau đó điều khiển các bản cập nhật view / DOM. Nếu bạn cập nhật DOM bên ngoài Angular, bạn sẽ phải cập nhật các mô hình theo cách thủ công. scope.$apply(scope.model)sẽ chỉ đơn giản là đánh giá scope.modelnhư một biểu thức Angular, và sau đó nhập một vòng lặp $ digest. Trong bài viết mà bạn tham khảo, có lẽ scope.$apply()sẽ là đủ, vì mô hình đã được $ watch'ed. Hàm stop () đang cập nhật mô hình (tôi tin rằng toUpdate là một tham chiếu đến scope.model), và sau đó $ áp dụng được gọi.
Mark Rajcok

Có vẻ như các tài liệu AngularJS đã chuyển ra từ dưới câu trả lời này (liên kết đầu tiên không có "thời gian chạy" hoặc $watchtrên trang và liên kết thứ hai bị hỏng - dù sao đi nữa, dù sao đi nữa). Thật đau đớn, các phiên bản lưu trữ đã không lưu trữ bất cứ quy trình async nào tạo ra nội dung.
ruffin

52

AngularJS mở rộng vòng lặp sự kiện này , tạo ra một cái gì đó được gọi là AngularJS context.

$ xem ()

Mỗi khi bạn liên kết một cái gì đó trong UI bạn sẽ chèn $watchmột $watchdanh sách .

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

Ở đây chúng ta có $scope.user, liên kết với đầu vào thứ nhất và chúng ta có $scope.pass, liên kết với đầu vào thứ hai. Làm điều này, chúng tôi thêm hai $watches vào $watchdanh sách .

Khi mẫu của chúng tôi được tải, AKA trong giai đoạn liên kết, trình biên dịch sẽ tìm mọi lệnh và tạo tất cả các $watches cần thiết.

AngularJS cung cấp $watch, $watchcollection$watch(true). Dưới đây là một sơ đồ gọn gàng giải thích tất cả ba được lấy từ người theo dõi sâu .

Nhập mô tả hình ảnh ở đây

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest vòng

Khi trình duyệt nhận được một sự kiện có thể được quản lý bởi bối cảnh AngularJS, $digestvòng lặp sẽ được kích hoạt. Vòng lặp này được làm từ hai vòng nhỏ hơn. Một xử lý $evalAsynchàng đợi, và một cái khác xử lý $watch list. Ý $digestchí sẽ lặp qua danh sách $watchmà chúng ta có

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

Ở đây chúng tôi chỉ có một $watchvì ng-click không tạo ra bất kỳ đồng hồ nào.

Chúng tôi nhấn nút.

  1. Trình duyệt nhận được một sự kiện sẽ đi vào bối cảnh AngularJS
  2. Các $digestvòng lặp sẽ chạy và sẽ yêu cầu mỗi $ đồng hồ cho những thay đổi.
  3. $watchcái đang theo dõi các thay đổi trong $ scope.name báo cáo một thay đổi, nó sẽ buộc một $digestvòng lặp khác .
  4. Vòng lặp mới báo cáo không có gì.
  5. Trình duyệt lấy lại quyền kiểm soát và nó sẽ cập nhật DOM phản ánh giá trị mới của $ scope.name
  6. Điều quan trọng ở đây là MỌI sự kiện đi vào bối cảnh AngularJS sẽ chạy một $digestvòng lặp. Điều đó có nghĩa là mỗi khi chúng ta viết một chữ cái trong một đầu vào, vòng lặp sẽ chạy kiểm tra mọi thứ $watchtrong trang này.

$ áp dụng ()

Nếu bạn gọi $applykhi một sự kiện được kích hoạt, nó sẽ đi qua bối cảnh góc cạnh, nhưng nếu bạn không gọi nó, nó sẽ chạy bên ngoài nó. Nó là dễ dàng như vậy. $applysẽ gọi$digest() vòng lặp bên trong và nó sẽ lặp lại trên tất cả các đồng hồ để đảm bảo DOM được cập nhật với giá trị mới được cập nhật.

Các $apply()phương pháp sẽ kích hoạt sát trên toàn bộ $scopechuỗi trong khi $digest()phương pháp sẽ chỉ kích hoạt sát vào hiện tại $scopevà nó children. Khi không có $scopeđối tượng cao cấp nào cần biết về những thay đổi cục bộ, bạn có thể sử dụng $digest().


18

Tôi thấy rất sâu video mà che $watch, $apply, $digestvà tiêu hóa chu kỳ trong:

Sau đây là một vài slide được sử dụng trong các video đó để giải thích các khái niệm (chỉ trong trường hợp, nếu các liên kết trên bị xóa / không hoạt động).

Nhập mô tả hình ảnh ở đây

Trong hình ảnh trên, "$ scope.c" không được xem vì nó không được sử dụng trong bất kỳ ràng buộc dữ liệu nào (trong đánh dấu). Hai ( $scope.a$scope.b) khác sẽ được theo dõi.

Nhập mô tả hình ảnh ở đây

Từ hình ảnh trên: Dựa trên sự kiện trình duyệt tương ứng, AngularJS nắm bắt sự kiện, thực hiện chu trình tiêu hóa (trải qua tất cả các đồng hồ để thay đổi), thực hiện các chức năng theo dõi và cập nhật DOM. Nếu không phải là sự kiện trình duyệt, chu trình phân loại có thể được kích hoạt bằng cách sử dụng $applyhoặc $digest.

Thêm về $apply$digest:

Nhập mô tả hình ảnh ở đây


17

$watchGroup$watchCollectionlà tốt. Cụ thể, $watchGroupthực sự hữu ích nếu bạn muốn gọi một hàm để cập nhật một đối tượng có nhiều thuộc tính trong một khung nhìn không phải là đối tượng dom, ví dụ như một khung nhìn khác trong canvas, WebGL hoặc yêu cầu máy chủ.

Ở đây, các liên kết tài liệu .


Tôi đã có ý kiến ​​về $watchCollectionnhưng tôi thấy bạn đã làm. Đây là tài liệu về nó từ trang AngularJS. Họ cung cấp một hình ảnh rất đẹp về $watchđộ sâu. Lưu ý thông tin ở gần cuối trang.
JabberwockyDecompiler

15

Chỉ cần đọc xong TẤT CẢ những điều trên, nhàm chán và buồn ngủ (xin lỗi nhưng là sự thật). Rất kỹ thuật, chuyên sâu, chi tiết và khô ráo. Tại sao tôi viết? Bởi vì AngularJS rất lớn, rất nhiều khái niệm liên kết với nhau có thể khiến bất cứ ai phát điên. Tôi thường tự hỏi mình, có phải tôi không đủ thông minh để hiểu họ? Không! Đó là bởi vì rất ít người có thể giải thích công nghệ bằng ngôn ngữ for-dummie với tất cả các thuật ngữ! Được rồi, để tôi thử:

1) Chúng đều là những thứ hướng đến sự kiện. (Tôi nghe thấy tiếng cười, nhưng đọc tiếp)

Nếu bạn không biết điều khiển sự kiện là gì thì hãy nghĩ rằng bạn đặt một nút trên trang, nối nó với chức năng "nhấp chuột", chờ người dùng nhấp vào nó để kích hoạt các hành động bạn thực hiện bên trong chức năng. Hoặc nghĩ về "trình kích hoạt" của SQL Server / Oracle.

2) $ xem là "nhấp chuột".

Điều đặc biệt là nó có 2 chức năng làm tham số, thứ nhất đưa ra giá trị từ sự kiện, thứ hai sẽ xem xét giá trị ...

3) $ digest là ông chủ kiểm tra không mệt mỏi , bla-bla-bla nhưng là một ông chủ tốt.

4) $ áp dụng cung cấp cho bạn cách khi bạn muốn thực hiện thủ công , như bằng chứng không thành công (trong trường hợp nhấp chuột không khởi động, bạn buộc nó phải chạy.)

Bây giờ, hãy làm cho nó trực quan. Hình ảnh này để làm cho nó dễ dàng hơn để lấy ý tưởng:

Trong một nhà hàng,

- BỒI BÀN

được cho là nhận đơn đặt hàng từ khách hàng, đây là

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- QUẢN LÝ chạy xung quanh để đảm bảo tất cả nhân viên phục vụ đều tỉnh táo, phản ứng nhanh với mọi dấu hiệu thay đổi từ khách hàng. Đây là$digest()

- CHỦ SỞ HỮU có sức mạnh tối thượng để thúc đẩy mọi người theo yêu cầu, đây là$apply()


2
Điều này có thể được hiểu bởi một đứa trẻ 5 tuổi. Tôi đánh giá cao loại câu trả lời này. +1
Chris22
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.