AngularJS: Ngăn chặn lỗi $ digest đang diễn ra khi gọi $ scope. $ Áp dụng ()


838

Tôi thấy rằng tôi cần cập nhật trang của mình theo phạm vi của mình theo cách thủ công ngày càng nhiều kể từ khi xây dựng một ứng dụng ở góc.

Cách duy nhất tôi biết để làm điều này là gọi $apply()từ phạm vi của bộ điều khiển và chỉ thị của tôi. Vấn đề với điều này là nó liên tục ném lỗi vào bảng điều khiển có nội dung:

Lỗi: $ digest đã được tiến hành

Có ai biết làm thế nào để tránh lỗi này hoặc đạt được điều tương tự nhưng theo một cách khác?


34
Điều thực sự bực bội là chúng ta cần sử dụng $ áp dụng ngày càng nhiều.
OZ_

Tôi cũng đang gặp lỗi này, mặc dù tôi đang gọi $ áp dụng trong một cuộc gọi lại. Tôi đang sử dụng thư viện của bên thứ ba để truy cập dữ liệu trên máy chủ của họ, vì vậy tôi không thể tận dụng $ http, tôi cũng không muốn vì tôi sẽ phải viết lại thư viện của họ để sử dụng $ http.
Trevor

45
sử dụng$timeout()
Onur Yıldırım

6
sử dụng $ timeout (fn) + 1, Nó có thể khắc phục sự cố,! $ scope. $$ pha không phải là giải pháp tốt nhất.
Huei Tan

1
Chỉ bao bọc mã / phạm vi cuộc gọi. $ Áp dụng từ trong thời gian chờ (không phải $ timeout) các hàm AJAX (không phải $ http) và các sự kiện (không ng-*). Đảm bảo, nếu bạn đang gọi nó từ bên trong một chức năng (được gọi thông qua thời gian chờ / ajax / sự kiện), thì nó cũng không được chạy khi tải ban đầu.
Patrick

Câu trả lời:


660

Không sử dụng mẫu này - Điều này sẽ gây ra nhiều lỗi hơn nó giải quyết. Mặc dù bạn nghĩ rằng nó đã sửa một cái gì đó, nhưng nó đã không.

Bạn có thể kiểm tra nếu a $digestđã được tiến hành bằng cách kiểm tra $scope.$$phase.

if(!$scope.$$phase) {
  //$digest or $apply
}

$scope.$$phasesẽ trở lại "$digest"hoặc "$apply"nếu một $digesthoặc $applyđang trong tiến trình. Tôi tin rằng sự khác biệt giữa các trạng thái này là $digestsẽ xử lý các đồng hồ thuộc phạm vi hiện tại và con của nó, và $applysẽ xử lý các đồng hồ của tất cả các phạm vi.

Theo quan điểm của @ dnc253, nếu bạn thấy mình gọi điện thoại $digesthoặc $applythường xuyên, bạn có thể đang làm sai. Tôi thường thấy rằng tôi cần phải tiêu hóa khi tôi cần cập nhật trạng thái của phạm vi do kết quả của một sự kiện DOM bắn ra ngoài tầm với của Angular. Ví dụ, khi một phương thức bootstrap twitter bị ẩn. Đôi khi sự kiện DOM kích hoạt khi một $digesttiến trình, đôi khi không. Đó là lý do tại sao tôi sử dụng kiểm tra này.

Tôi rất muốn biết một cách tốt hơn nếu bất cứ ai biết một.


Từ ý kiến: bởi @anddoutoi

angular.js Anti Forms

  1. Đừng làm vậy if (!$scope.$$phase) $scope.$apply(), điều đó có nghĩa là bạn $scope.$apply()không đủ cao trong ngăn xếp cuộc gọi.

230
Dường như với tôi như $ digest / $ áp dụng nên làm điều này theo mặc định
Roy Truelove

21
Lưu ý rằng trong một số trường hợp tôi phải kiểm tra nhưng phạm vi hiện tại VÀ phạm vi gốc. Tôi đã nhận được một giá trị cho giai đoạn $$ trên root nhưng không phải trên phạm vi của tôi. Nghĩ rằng nó có liên quan đến phạm vi biệt lập của một chỉ thị, nhưng ..
Roy Truelove

106
"Dừng làm if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Potypes
anddoutoi

34
@anddoutoi: Đồng ý; liên kết của bạn làm cho nó khá rõ ràng đây không phải là giải pháp; tuy nhiên, tôi không chắc ý nghĩa của "bạn không đủ cao trong ngăn xếp cuộc gọi". Bạn có biết nó có nghĩa là gì không?
Trevor

13
@threed: xem câu trả lời của aaronfrost. Cách chính xác là sử dụng defer để kích hoạt thông báo trong chu kỳ tiếp theo. Nếu không, sự kiện sẽ bị mất và không cập nhật phạm vi nào cả.
Marek

663

Từ một cuộc thảo luận gần đây với các anh chàng Angular về chính chủ đề này: Vì những lý do chứng minh trong tương lai, bạn không nên sử dụng$$phase

Khi nhấn vào cách "đúng" để làm điều đó, câu trả lời hiện tại

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

Gần đây tôi đã gặp phải điều này khi viết các dịch vụ góc cạnh để bọc các API của facebook, google và twitter, ở các mức độ khác nhau, có các cuộc gọi lại được đưa vào.

Đây là một ví dụ từ trong một dịch vụ. (Vì lợi ích của sự ngắn gọn, phần còn lại của dịch vụ - thiết lập các biến, đã chèn $ timeout, v.v. - đã bị loại bỏ.)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

Lưu ý rằng đối số chậm trễ với $ timeout là tùy chọn và sẽ mặc định là 0 nếu trái unset ( $ timeout gọi $ browser.defergiá trị mặc định là 0 nếu chậm trễ không được thiết lập )

Một chút không trực quan, nhưng đó là câu trả lời từ những người viết Angular, vì vậy nó đủ tốt cho tôi!


5
Tôi đã gặp phải điều này nhiều lần trong các chỉ thị của mình. Đã viết một cho redactor và điều này hóa ra hoạt động hoàn hảo. Tôi đã có cuộc gặp gỡ với Brad Green và anh ấy nói rằng Angular 2.0 sẽ rất lớn khi không có chu kỳ tiêu hóa bằng khả năng quan sát tự nhiên của JS và sử dụng một polyfill cho các trình duyệt thiếu điều đó. Tại thời điểm đó, chúng tôi sẽ không cần phải làm điều này nữa. :)
Michael J. Calkins

Hôm qua tôi đã thấy một vấn đề trong đó việc gọi selectize.refreshItems () bên trong $ timeout gây ra lỗi tiêu hóa đệ quy đáng sợ. Bất kỳ ý tưởng làm thế nào có thể được?
iwein

3
Nếu bạn sử dụng $timeoutchứ không phải bản địa setTimeout, tại sao bạn không sử dụng $windowthay vì bản địa window?
LeeGee

2
@LeeGee: Điểm sử dụng $timeouttrong trường hợp này là $timeoutđảm bảo rằng phạm vi góc được cập nhật đúng. Nếu $ digest không được tiến hành, nó sẽ khiến $ digest mới chạy.
kinh ngạc

2
@webicy Đó không phải là một điều. Khi phần thân của hàm được chuyển đến $ timeout được chạy, lời hứa đã được giải quyết! Hoàn toàn không có lý do cho cancelnó. Từ các tài liệu : "Kết quả của việc này, lời hứa sẽ được giải quyết bằng một lời từ chối." Bạn không thể giải quyết một lời hứa đã giải quyết. Việc hủy bỏ của bạn sẽ không gây ra bất kỳ lỗi nào, nhưng nó cũng sẽ không làm điều gì tích cực.
daemonexmachina

324

Chu trình digest là một cuộc gọi đồng bộ. Nó sẽ không mang lại quyền kiểm soát cho vòng lặp sự kiện của trình duyệt cho đến khi hoàn thành. Có một vài cách để đối phó với điều này. Cách dễ nhất để giải quyết vấn đề này là sử dụng thời gian chờ $ dựng sẵn, và cách thứ hai là nếu bạn đang sử dụng dấu gạch dưới hoặc ký tự (và bạn nên như vậy), hãy gọi như sau:

$timeout(function(){
    //any code in here will automatically have an apply run afterwards
});

hoặc nếu bạn có lodash:

_.defer(function(){$scope.$apply();});

Chúng tôi đã thử một số cách giải quyết và chúng tôi ghét tiêm $ rootScope vào tất cả các bộ điều khiển, chỉ thị và thậm chí một số nhà máy của chúng tôi. Vì vậy, $ timeout và _.defer là mục yêu thích của chúng tôi cho đến nay. Các phương thức này cho phép góc cạnh chờ đợi cho đến vòng lặp hoạt hình tiếp theo, điều này sẽ đảm bảo rằng phạm vi hiện tại. $ Áp dụng đã kết thúc.


2
Điều này có thể so sánh với việc sử dụng $ timeout (...) không? Tôi đã sử dụng $ timeout trong một số trường hợp để trì hoãn chu kỳ sự kiện tiếp theo và nó dường như hoạt động tốt - có ai biết liệu có lý do gì để không sử dụng $ timeout không?
Trevor

9
Điều này thực sự chỉ nên được sử dụng nếu bạn đã sử dụng underscore.js. Giải pháp này không đáng để nhập toàn bộ thư viện gạch dưới chỉ để sử dụng deferchức năng của nó . Tôi rất thích $timeoutgiải pháp này vì mọi người đều có quyền truy cập $timeoutthông qua các góc, không có bất kỳ sự phụ thuộc nào vào các thư viện khác.
quần vợt

10
Đúng ... nhưng nếu bạn không sử dụng dấu gạch dưới hoặc dấu gạch ngang ... bạn cần đánh giá lại những gì bạn đang làm. Hai lib đó đã thay đổi giao diện của mã.
băng giá

2
Chúng tôi có lodash như một sự phụ thuộc cho Restangular (chúng tôi sẽ sớm loại bỏ Restangular để ủng hộ tuyến đường ng). Tôi nghĩ rằng đó là một câu trả lời tốt nhưng thật tuyệt khi cho rằng mọi người muốn sử dụng dấu gạch dưới / lodash. Bằng mọi cách, những lib đó vẫn ổn ... nếu bạn sử dụng chúng đủ ... ngày nay tôi sử dụng các phương pháp ES5 để loại bỏ 98% lý do tôi sử dụng để bao gồm dấu gạch dưới.
BradGreen

2
Bạn nói đúng @SgtPooki. Tôi đã sửa đổi câu trả lời để bao gồm tùy chọn sử dụng $ timeout. $ timeout và _.defer đều sẽ đợi cho đến vòng lặp hoạt hình tiếp theo, điều này sẽ đảm bảo rằng phạm vi hiện tại. $ áp dụng đã kết thúc. Cảm ơn vì đã giữ cho tôi trung thực và để tôi cập nhật câu trả lời ở đây.
băng giá

267

Nhiều câu trả lời ở đây chứa những lời khuyên tốt nhưng cũng có thể dẫn đến nhầm lẫn. Đơn giản chỉ cần sử dụng không phải$timeout là giải pháp tốt nhất và đúng đắn. Ngoài ra, hãy chắc chắn đọc rằng nếu bạn quan tâm bởi hiệu suất hoặc khả năng mở rộng.

Những điều bạn nên biết

  • $$phase là riêng tư cho khuôn khổ và có những lý do tốt cho điều đó.

  • $timeout(callback)sẽ đợi cho đến khi chu trình phân loại hiện tại (nếu có) được thực hiện, sau đó thực hiện gọi lại, sau đó chạy ở cuối đầy đủ $apply.

  • $timeout(callback, delay, false)sẽ làm tương tự (với độ trễ tùy chọn trước khi thực hiện cuộc gọi lại), nhưng sẽ không kích hoạt $apply(đối số thứ ba) để lưu hiệu suất nếu bạn không sửa đổi mô hình Angular của mình ($ scope).

  • $scope.$apply(callback)các lệnh gọi, trong số những thứ khác $rootScope.$digest, có nghĩa là nó sẽ chuyển hướng phạm vi gốc của ứng dụng và tất cả các con của nó, ngay cả khi bạn ở trong một phạm vi biệt lập.

  • $scope.$digest()sẽ đơn giản đồng bộ mô hình của nó với chế độ xem, nhưng sẽ không tiêu hóa phạm vi cha mẹ của nó, điều này có thể tiết kiệm rất nhiều hiệu suất khi làm việc trên một phần bị cô lập của HTML với phạm vi tách biệt (chủ yếu là từ chỉ thị). $ digest không thực hiện cuộc gọi lại: bạn thực thi mã, sau đó phân loại.

  • $scope.$evalAsync(callback)đã được giới thiệu với angularjs 1.2, và có thể sẽ giải quyết hầu hết các rắc rối của bạn. Vui lòng tham khảo đoạn cuối để tìm hiểu thêm về nó.

  • nếu bạn nhận được $digest already in progress error, thì kiến ​​trúc của bạn sai: hoặc bạn không cần phải chuyển hướng phạm vi của mình, hoặc bạn không nên chịu trách nhiệm về điều đó (xem bên dưới).

Cách cấu trúc mã của bạn

Khi bạn gặp lỗi đó, bạn đang cố gắng tiêu hóa phạm vi của mình trong khi nó đang diễn ra: vì bạn không biết trạng thái phạm vi của mình tại thời điểm đó, bạn không chịu trách nhiệm xử lý sự tiêu hóa của nó.

function editModel() {
  $scope.someVar = someVal;
  /* Do not apply your scope here since we don't know if that
     function is called synchronously from Angular or from an
     asynchronous code */
}

// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
  // No need to digest
  editModel();
}

// Any kind of asynchronous code, for instance a server request
callServer(function() {
  /* That code is not watched nor digested by Angular, thus we
     can safely $apply it */
  $scope.$apply(editModel);
});

Và nếu bạn biết những gì bạn đang làm và làm việc trên một chỉ thị nhỏ bị cô lập trong khi là một phần của ứng dụng Angular lớn, bạn có thể thích $ digest thay vì $ áp dụng để lưu hiệu suất.

Cập nhật kể từ Angularjs 1.2

Một phương thức mới, mạnh mẽ đã được thêm vào bất kỳ phạm vi $ nào: $evalAsync . Về cơ bản, nó sẽ thực hiện cuộc gọi lại của nó trong chu kỳ phân loại hiện tại nếu xảy ra, nếu không một chu trình phân loại mới sẽ bắt đầu thực hiện cuộc gọi lại.

Điều đó vẫn không tốt bằng $scope.$digestnếu bạn thực sự biết rằng bạn chỉ cần đồng bộ hóa một phần bị cô lập trong HTML của mình (vì một phần mới $applysẽ được kích hoạt nếu không có gì đang diễn ra), nhưng đây là giải pháp tốt nhất khi bạn đang thực thi một chức năng mà bạn không thể biết nó có được thực thi đồng bộ hay không , chẳng hạn như sau khi tìm nạp tài nguyên có khả năng lưu trong bộ nhớ cache: đôi khi điều này sẽ yêu cầu một cuộc gọi không đồng bộ đến máy chủ, nếu không tài nguyên sẽ được tìm nạp đồng bộ cục bộ.

Trong những trường hợp này và tất cả những trường hợp khác mà bạn đã có !$scope.$$phase, hãy chắc chắn sử dụng$scope.$evalAsync( callback )


4
$timeoutđược phê bình trong việc thông qua. Bạn có thể đưa ra nhiều lý do để tránh $timeout?
mlhDev

88

Phương pháp trợ giúp nhỏ tiện dụng để giữ quá trình này KHÔ:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}

6
An toàn của bạn đã giúp tôi hiểu những gì đang xảy ra nhiều hơn bất cứ điều gì khác. Cảm ơn đã đăng bài đó.
Jason More

4
Tôi cũng sắp làm điều tương tự, nhưng không làm điều này có nghĩa là có khả năng những thay đổi chúng tôi thực hiện trong fn () sẽ không được nhìn thấy bởi $ digest? Sẽ không tốt hơn nếu trì hoãn chức năng, giả sử phạm vi. $$ phase === '$ digest'?
Spencer Alger

Tôi đồng ý, đôi khi $ áp dụng () được sử dụng để kích hoạt thông báo, chỉ cần gọi fn một mình ... sẽ không dẫn đến vấn đề?
CMCDragonkai

1
Tôi cảm thấy scope.$apply(fn);nên như vậy scope.$apply(fn());bởi vì fn () sẽ thực thi hàm chứ không phải fn. Xin hãy giúp tôi đến nơi tôi sai
madhu131313

1
@ZenOut Cuộc gọi đến $ áp dụng hỗ trợ nhiều loại đối số khác nhau, bao gồm cả các hàm. Nếu thông qua một chức năng, nó đánh giá chức năng.
boxmein

33

Tôi gặp vấn đề tương tự với các tập lệnh của bên thứ ba như CodeMirror chẳng hạn và Krpano, và thậm chí sử dụng các phương thức safeApply được đề cập ở đây cũng không giải quyết được lỗi cho tôi.

Nhưng những gì đã giải quyết nó là sử dụng dịch vụ $ timeout (đừng quên tiêm trước).

Vì vậy, một cái gì đó như:

$timeout(function() {
  // run my code safely here
})

và nếu bên trong mã của bạn, bạn đang sử dụng

điều này

có lẽ bởi vì nó nằm trong bộ điều khiển của chỉ thị của nhà máy hoặc chỉ cần một loại ràng buộc nào đó, nên bạn sẽ làm một cái gì đó như:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)

32

Xem http://docs.angularjs.org/error/$rootScope:inprog

Vấn đề phát sinh khi bạn có một cuộc gọi đến $applyđôi khi được chạy không đồng bộ bên ngoài mã Angular (khi sử dụng $ nên được sử dụng) và đôi khi đồng bộ bên trong mã Angular (gây ra $digest already in progresslỗi).

Điều này có thể xảy ra, ví dụ, khi bạn có một thư viện tìm nạp không đồng bộ các mục từ máy chủ và lưu trữ chúng. Lần đầu tiên một mục được yêu cầu, nó sẽ được truy xuất không đồng bộ để không chặn thực thi mã. Tuy nhiên, lần thứ hai, mục đã có trong bộ đệm để có thể truy xuất đồng bộ.

Cách để ngăn chặn lỗi này là đảm bảo rằng mã mà các cuộc gọi $applyđược chạy không đồng bộ. Điều này có thể được thực hiện bằng cách chạy mã của bạn trong một cuộc gọi đến $timeoutvới độ trễ được đặt thành 0(là mặc định). Tuy nhiên, việc gọi mã của bạn bên trong $timeoutsẽ loại bỏ sự cần thiết phải gọi $apply, vì $ timeout sẽ kích hoạt một mã khác$digest chu kỳ , do đó sẽ thực hiện tất cả các cập nhật cần thiết, v.v.

Giải pháp

Nói tóm lại, thay vì làm điều này:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

làm cái này:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

Chỉ gọi $applykhi bạn biết mã đang chạy, mã sẽ luôn được chạy bên ngoài mã Angular (ví dụ: lệnh gọi tới $ áp dụng của bạn sẽ xảy ra bên trong một cuộc gọi lại được gọi bằng mã bên ngoài mã Angular của bạn).

Trừ khi một người nào đó là nhận thức của một số bất lợi ảnh hưởng lớn đến việc sử dụng $timeoutqua $apply, tôi không thấy lý do tại sao bạn có thể không luôn luôn sử dụng $timeout(với không chậm trễ) thay vì $apply, vì nó sẽ làm khoảng điều tương tự.


Cảm ơn, điều này đã làm việc cho trường hợp của tôi khi tôi không $applytự gọi mình nhưng vẫn nhận được lỗi.
ariscris

5
Sự khác biệt chính $applylà đồng bộ (cuộc gọi lại của nó được thực thi, sau đó mã theo $ áp dụng) trong khi $timeoutkhông phải là: mã hiện tại sau khi hết thời gian được thực thi, sau đó một ngăn xếp mới bắt đầu với cuộc gọi lại của nó, như thể bạn đang sử dụng setTimeout. Điều đó có thể dẫn đến sự cố đồ họa nếu bạn đang cập nhật hai lần cùng một mô hình: $timeoutsẽ đợi chế độ xem được làm mới trước khi cập nhật lại.
floribon

Cảm ơn thực sự, tham lam. Tôi đã có một phương thức được gọi là kết quả của một số hoạt động $ watch và đang cố cập nhật giao diện người dùng trước khi bộ lọc bên ngoài của tôi đã thực hiện xong. Đặt nó bên trong một hàm thời gian chờ $ làm việc cho tôi.
djmarquette

28

Khi bạn gặp lỗi này, về cơ bản, điều đó có nghĩa là nó đã trong quá trình cập nhật chế độ xem của bạn. Bạn thực sự không cần phải gọi $apply()trong bộ điều khiển của bạn. Nếu chế độ xem của bạn không cập nhật như bạn mong đợi, và sau đó bạn gặp lỗi này sau khi gọi $apply(), rất có thể có nghĩa là bạn không cập nhật mô hình chính xác. Nếu bạn đăng một số chi tiết cụ thể, chúng tôi có thể tìm ra vấn đề cốt lõi.


heh, tôi đã dành cả ngày để tìm hiểu rằng AngularJS chỉ không thể xem các ràng buộc "một cách kỳ diệu" và đôi khi tôi nên đẩy anh ta bằng $ áp dụng ().
OZ_

Có nghĩa là you're not updating the the model correctlygì? $scope.err_message = 'err message';không cập nhật đúng?
OZ_

2
Lần duy nhất bạn cần gọi $apply()là khi bạn cập nhật mô hình "bên ngoài" góc cạnh (ví dụ: từ một plugin jQuery). Thật dễ dàng để rơi vào cái bẫy của chế độ xem không đúng, và vì vậy bạn ném một loạt các $apply()s ở khắp mọi nơi, sau đó kết thúc với lỗi nhìn thấy trong OP. Khi tôi nói you're not updating the the model correctlytôi chỉ có nghĩa là tất cả logic kinh doanh không điền chính xác bất cứ thứ gì có thể có trong phạm vi, dẫn đến chế độ xem không như mong đợi.
dnc253

@ dnc253 Tôi đồng ý, và tôi đã viết câu trả lời. Biết những gì tôi biết bây giờ, tôi sẽ sử dụng $ timeout (function () {...}); Nó làm điều tương tự như _.defer. Cả hai đều trì hoãn vòng lặp hoạt hình tiếp theo.
sương giá

14

Hình thức an toàn ngắn nhất $applylà:

$timeout(angular.noop)

11

Bạn cũng có thể sử dụng evalAsync. Nó sẽ chạy đôi khi sau khi tiêu hóa xong!

scope.evalAsync(function(scope){
    //use the scope...
});

10

Trước hết, đừng sửa nó theo cách này

if ( ! $scope.$$phase) { 
  $scope.$apply(); 
}

Điều này không có ý nghĩa gì vì $ phase chỉ là một cờ boolean cho chu kỳ $ digest, vì vậy đôi khi $ áp dụng () của bạn sẽ không chạy. Và hãy nhớ rằng đó là một thực hành xấu.

Thay vào đó, sử dụng $timeout

    $timeout(function(){ 
  // Any code in here will automatically have an $scope.apply() run afterwards 
$scope.myvar = newValue; 
  // And it just works! 
});

Nếu bạn đang sử dụng gạch dưới hoặc lodash, bạn có thể sử dụng defer ():

_.defer(function(){ 
  $scope.$apply(); 
});

9

Đôi khi bạn vẫn sẽ gặp lỗi nếu bạn sử dụng cách này ( https://stackoverflow.com/a/12859093/801426 ).

Thử cái này:

if(! $rootScope.$root.$$phase) {
...

5
sử dụng cả hai! $ scope. $$ phase và! $ scope. $ root. $$ phase (not! $ rootScope. $ root. $$ phase) hoạt động với tôi. +1
asprotte

2
$rootScopeanyScope.$rootlà cùng một anh chàng. $rootScope.$rootlà dư thừa.
floribon


5

thử sử dụng

$scope.applyAsync(function() {
    // your code
});

thay vì

if(!$scope.$$phase) {
  //$digest or $apply
}

$ áp dụng Đồng bộ hóa Lịch trình việc gọi $ áp dụng sẽ xảy ra sau đó. Điều này có thể được sử dụng để xếp hàng nhiều biểu thức cần được đánh giá trong cùng một thông báo.

LƯU Ý: Trong $ digest, $ applicationAsync () sẽ chỉ xóa nếu phạm vi hiện tại là $ rootScope. Điều này có nghĩa là nếu bạn gọi $ digest trên phạm vi con, nó sẽ không hoàn toàn xóa hàng đợi $ applicationAsync ().

Sơ đồ:

  $scope.$applyAsync(function () {
                if (!authService.authenticated) {
                    return;
                }

                if (vm.file !== null) {
                    loadService.setState(SignWizardStates.SIGN);
                } else {
                    loadService.setState(SignWizardStates.UPLOAD_FILE);
                }
            });

Người giới thiệu:

1. Phạm vi. $ ApplicationAsync () so với Phạm vi. $ EvalAsync () trong AngularJS 1.3

  1. Tài liệu AngularJs

4

Tôi sẽ khuyên bạn nên sử dụng một sự kiện tùy chỉnh thay vì kích hoạt một chu trình tiêu hóa.

Tôi đã phát hiện ra rằng phát sóng các sự kiện tùy chỉnh và đăng ký người nghe cho sự kiện này là một giải pháp tốt để kích hoạt một hành động bạn muốn xảy ra cho dù bạn có đang trong chu kỳ tiêu hóa hay không.

Bằng cách tạo một sự kiện tùy chỉnh, bạn cũng sẽ hiệu quả hơn với mã của mình vì bạn chỉ kích hoạt người nghe đăng ký vào sự kiện đã nói và KHÔNG kích hoạt tất cả đồng hồ bị ràng buộc trong phạm vi như bạn muốn nếu bạn gọi phạm vi. $ Áp dụng.

$scope.$on('customEventName', function (optionalCustomEventArguments) {
   //TODO: Respond to event
});


$scope.$broadcast('customEventName', optionalCustomEventArguments);

3

yearofmoo đã làm rất tốt trong việc tạo ra một hàm $ safeApply có thể sử dụng lại cho chúng tôi:

https://github.com/yearofmoo/AngularJS-Scope.SafeApply

Sử dụng :

//use by itself
$scope.$safeApply();

//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);

//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {

});

//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {

});

//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);

2

Tôi đã có thể giải quyết vấn đề này bằng cách gọi $evalthay vì $applyở những nơi tôi biết rằng $digestchức năng sẽ được chạy.

Theo các tài liệu , $applyvề cơ bản làm điều này:

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

Trong trường hợp của tôi, một ng-clickthay đổi một biến trong phạm vi và $ watch trên biến đó thay đổi các biến khác phải là$applied . Bước cuối cùng này gây ra lỗi "tiêu hóa đã được tiến hành".

Bằng cách thay thế $applybằng$eval bên trong biểu thức đồng hồ, các biến phạm vi được cập nhật như mong đợi.

Do đó, có vẻ như nếu digest sẽ chạy bất cứ cách nào vì một số thay đổi khác trong Angular, $eval'ing là tất cả những gì bạn cần làm.



1

Hiểu rằng các tài liệu góc gọi kiểm tra $$phasemột chống mẫu , tôi đã cố gắng để có được $timeout_.deferlàm việc.

Các phương thức hết thời gian chờ và hoãn lại tạo ra một {{myVar}}nội dung không được chỉnh sửa trong dom giống như một FOUT . Đối với tôi điều này là không thể chấp nhận được. Nó khiến tôi không có nhiều điều để nói một cách giáo điều rằng một cái gì đó là một hack, và không có một sự thay thế phù hợp.

Điều duy nhất hoạt động mọi lúc là:

if(scope.$$phase !== '$digest'){ scope.$digest() }.

Tôi không hiểu sự nguy hiểm của phương pháp này, hoặc tại sao nó được mô tả là hack bởi những người trong các bình luận và nhóm góc cạnh. Lệnh có vẻ chính xác và dễ đọc:

"Làm tiêu hóa trừ khi một cái đã xảy ra"

Trong CoffeeScript, nó thậm chí còn đẹp hơn:

scope.$digest() unless scope.$$phase is '$digest'

Có vấn đề gì với điều này? Có một sự thay thế nào sẽ không tạo ra một KHÔNG? $ safeApply trông cũng ổn nhưng cũng sử dụng $$phasephương pháp kiểm tra.


1
Tôi muốn thấy một câu trả lời có căn cứ cho câu hỏi này!
Ben Wheeler

Đây là một hack vì điều đó có nghĩa là bạn bỏ lỡ ngữ cảnh hoặc không hiểu mã ở điểm này: hoặc bạn đang ở trong chu trình tiêu hóa góc và bạn không cần điều đó, hoặc bạn không đồng bộ bên ngoài điều đó và sau đó bạn cần nó. Nếu bạn không thể biết rằng tại thời điểm đó của mã, thì bạn không có trách nhiệm tiêu hóa nó
floribon

1

Đây là dịch vụ tiện ích của tôi:

angular.module('myApp', []).service('Utils', function Utils($timeout) {
    var Super = this;

    this.doWhenReady = function(scope, callback, args) {
        if(!scope.$$phase) {
            if (args instanceof Array)
                callback.apply(scope, Array.prototype.slice.call(args))
            else
                callback();
        }
        else {
            $timeout(function() {
                Super.doWhenReady(scope, callback, args);
            }, 250);
        }
    };
});

và đây là một ví dụ cho việc sử dụng nó:

angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
    $scope.foo = function() {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.foo);

    $scope.fooWithParams = function(p1, p2) {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};

1

Tôi đã sử dụng phương pháp này và nó dường như hoạt động hoàn toàn tốt. Điều này chỉ chờ thời gian chu kỳ kết thúc và sau đó kích hoạt apply(). Đơn giản chỉ cần gọi chức năng apply(<your scope>)từ bất cứ nơi nào bạn muốn.

function apply(scope) {
  if (!scope.$$phase && !scope.$root.$$phase) {
    scope.$apply();
    console.log("Scope Apply Done !!");
  } 
  else {
    console.log("Scheduling Apply after 200ms digest cycle already in progress");
    setTimeout(function() {
        apply(scope)
    }, 200);
  }
}

1

Khi tôi tắt trình gỡ lỗi, lỗi không xảy ra nữa. Trong trường hợp của tôi , đó là do trình gỡ lỗi dừng thực thi mã.


0

tương tự như câu trả lời ở trên nhưng điều này đã làm việc trung thành với tôi ... trong một dịch vụ thêm:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };

0

Bạn có thể dùng

$timeout

để ngăn chặn lỗi.

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);

Điều gì xảy ra nếu tôi không muốn sử dụng $ timeout
rahim.nagori

0

Vấn đề về cơ bản là đến khi, chúng tôi đang yêu cầu góc để chạy chu trình tiêu hóa mặc dù trong quá trình đó đang tạo ra vấn đề để hiểu được. hậu quả ngoại lệ trong giao diện điều khiển.
1. Không có bất kỳ ý nghĩa nào để gọi phạm vi. $ Áp dụng () bên trong hàm $ timeout vì bên trong nó thực hiện tương tự.
2. Mã đi với hàm JavaScript vanilla vì hàm góc không phải là góc được định nghĩa tức là setTimeout
3. Để làm điều đó, bạn có thể sử dụng

if (! Scope. $$ phase) {
scope. $ EvalAsync (function () {

}); }


0
        let $timeoutPromise = null;
        $timeout.cancel($timeoutPromise);
        $timeoutPromise = $timeout(() => {
            $scope.$digest();
        }, 0, false);

Đây là giải pháp tốt để tránh lỗi này và tránh áp dụng $

bạn có thể kết hợp điều này với gỡ lỗi (0) nếu gọi dựa trên sự kiện bên ngoài. Trên đây là 'gỡ lỗi' mà chúng tôi đang sử dụng và ví dụ đầy đủ về mã

.factory('debounce', [
    '$timeout',
    function ($timeout) {

        return function (func, wait, apply) {
            // apply default is true for $timeout
            if (apply !== false) {
                apply = true;
            }

            var promise;
            return function () {
                var cntx = this,
                    args = arguments;
                $timeout.cancel(promise);
                promise = $timeout(function () {
                    return func.apply(cntx, args);
                }, wait, apply);
                return promise;
            };
        };
    }
])

và chính mã để nghe một số sự kiện và chỉ gọi $ digest trên $ scope bạn cần

        let $timeoutPromise = null;
        let $update = debounce(function () {
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
        }, 0, false);

        let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
            $update();
        });


        $scope.$on('$destroy', () => {
            $timeout.cancel($update);
            $timeout.cancel($timeoutPromise);
            $unwatchModelChanges();
        });

-3

Đã tìm thấy điều này: https://coderwall.com/p/ngisma nơi Nathan Walker (gần cuối trang) gợi ý một người trang trí trong $ rootScope để tạo func 'safeApply', mã:

yourAwesomeModule.config([
  '$provide', function($provide) {
    return $provide.decorator('$rootScope', [
      '$delegate', function($delegate) {
        $delegate.safeApply = function(fn) {
          var phase = $delegate.$$phase;
          if (phase === "$apply" || phase === "$digest") {
            if (fn && typeof fn === 'function') {
              fn();
            }
          } else {
            $delegate.$apply(fn);
          }
        };
        return $delegate;
      }
    ]);
  }
]);

-7

Điều này sẽ được giải quyết vấn đề của bạn:

if(!$scope.$$phase) {
  //TODO
}

Đừng làm nếu (! $ Phạm vi. $$ pha) $ scope. $ Áp dụng (), điều đó có nghĩa là phạm vi $ của bạn. $ Áp dụng () không đủ cao trong ngăn xếp cuộc gọi.
MGot90
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.