2014-03-12 42 views
84

有時我需要在我的代碼中使用$scope.$apply,有時它會拋出「摘要已在進行中」錯誤。所以我開始找到解決辦法,發現這個問題:AngularJS : Prevent error $digest already in progress when calling $scope.$apply()。然而,在評論(以及在維基上),您可以閱讀:

不要這樣做(!$ scope。$$階段)$ scope。$ apply(),它表示您的$ scope。$調用堆棧中apply()不夠高。

所以現在我有兩個問題:

  1. 究竟爲什麼這是一個反模式?
  2. 如何安全地使用$ scope。$ apply?

另一種「解決方案」,以防止「消化正在進行中」的錯誤好像是用$超時:

$timeout(function() { 
    //... 
}); 

是不是要走的路?它更安全嗎?所以這裏是真正的問題:我怎樣才能完全消除「摘要已在進行中」錯誤的可能性?

PS:我只使用$ scope。$ apply在非同步的非angularjs回調函數中。 (據我知道這些情況下,你必須使用$範圍的情況下,如果要應用更改$應用)

+0

從我的經驗,你應該總是知道的,如果你是從內部的角度或從操作'scope'在角度之外。所以根據這個你總是知道,如果你需要調用'scope。$ apply'或不。如果你使用相同的代碼來處理角度/非角度的「範圍」操作,那麼你做錯了,它應該總是分開的......所以基本上,如果遇到需要檢查範圍的情況。$$階段',你的代碼沒有以正確的方式設計,並且總是有一種方法可以'正確的方式' – doodeec

+1

我只在非角度回調中使用它(!)這就是爲什麼我困惑 –

+2

如果它是非角度的,它不會拋出已經在進行中的'摘要'錯誤 – doodeec

回答

106

經過多次挖掘,我能夠解決使用$scope.$apply是否安全的問題。簡短的答案是肯定的。

龍答:

如何瀏覽器的Javascript執行到期,它是不可能有兩個消化通話偶然碰撞

我們編寫的JavaScript代碼並不是全部運行,而是輪流執行。每一輪都從頭到尾不間斷地運行,當一個轉彎正在運行時,我們的瀏覽器中沒有發生任何其他事情。 (從http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

因此錯誤「進行消化已經」只能出現在一個情況:當$敷另一個$內部發出申請,如:

$scope.apply(function() { 
    // some code... 
    $scope.apply(function() { ... }); 
}); 

這種情況可以不是產生如果我們使用$ scope.apply在一個純的非angularjs回調中,例如setTimeout的回調。所以下面的代碼是100%防彈並沒有沒有需要做if (!$scope.$$phase) $scope.$apply()

setTimeout(function() { 
    $scope.$apply(function() { 
     $scope.message = "Timeout called!"; 
    }); 
}, 2000); 

即使這個人是安全的:

$scope.$apply(function() { 
    setTimeout(function() { 
     $scope.$apply(function() { 
      $scope.message = "Timeout called!"; 
     }); 
    }, 2000); 
}); 

什麼是安全(因爲$超時 - 像所有角色幫手 - 已經爲你撥打$scope.$apply):

$timeout(function() { 
    $scope.$apply(function() { 
     $scope.message = "Timeout called!"; 
    }); 
}, 2000); 

這也解釋了爲什麼if (!$scope.$$phase) $scope.$apply()的使用是反模式。如果您以正確的方式使用$scope.$apply,則根本不需要它:例如,在純粹的js回調中,例如setTimeout

閱讀http://jimhoskins.com/2012/12/17/angularjs-and-apply.html的更詳細的解釋。

+0

我得到了一個例子,用 ''$文檔創建服務。bind('keydown',function(e){0} {0} {0} $ rootScope。$ apply(function(){ //從控制器傳遞的函數在這裏執行得到 }); });''' 我真的不會'不知道爲什麼我必須在這裏申請$,因爲我使用$ document.bind .. –

+0

,因爲$ document只是「瀏覽器的window.document對象的一個​​jQuery或jqLit​​e包裝器。」並執行如下:'''function $ DocumentProvider(){ this。$ get = ['$ window',function(window){ return jqLit​​e(window.document); }]; }'''那裏沒有適用。 –

+0

啊謝謝你:)所以一個問題是你的第一個保存應用的代碼示例,就像使用''timeout(function(){})''? –

9

scope.$apply觸發$digest週期,這是基本的2路數據綁定

$digest週期檢查對象,即與$scope相連的模型(確切地說是$watch),以評估它們的值是否已更改,如果檢測到更改,則將採取必要步驟更新視圖。

現在,當您使用$scope.$apply時,您將面臨一個錯誤「Already in progress」,因此很顯然$ digest正在運行,但觸發它的是什麼?

ans - >每$http調用,所有ng-click,重複,顯示,隱藏等觸發$digest週期和每個$範圍的最壞的部分運行。

即說你的頁面有4個控制器或指令A,B,C,d

如果你在他們每個人的4和$scope性能,那麼你一共有16 $作用域屬性頁面上。

如果在控制器D中觸發$scope.$apply,那麼$digest週期將檢查所有16個值!加上所有$ rootScope屬性。

回答 - >$scope.$digest觸發$digest對子和同一範圍,因此它將只檢查4個屬性。因此,如果您確定D中的更改不會影響A,B,C,則使用$scope.$diges而不是$scope.$apply

所以僅僅NG點擊或NG-顯示/隱藏可能超過100+性能觸發$digest週期,即使用戶已經不會觸發任何事件

+2

是的,我很遺憾地意識到這個問題。如果我從一開始就知道這一點,就不會使用Angular。 所有標準指令觸發一個$ scope。$ apply,後者又調用$ rootScope。$ digest,它在所有範圍上執行髒檢查。如果你問我,可能會造成設計決定不當我應該控制哪些範圍應該被檢查,因爲我知道數據如何連接到這些範圍! – MoonStom

10

在任何情況下,當您的摘要正在進行中,並且您推送另一個服務進行摘要時,它只會提供一個錯誤,即摘要已在進行中。 所以要解決這個問題你有兩個選擇。 您可以檢查其他正在進行中的摘要,如輪詢。

第一個

if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { 
    $scope.$apply(); 
} 

如果上述條件爲真,那麼你可以申請你的$範圍。$適用otherwies不和

第二個解決方案是使用$超時

$timeout(function() { 
    //... 
}) 

它不會讓其他摘要啓動直到$ timeout完成它是執行。

+1

downvoted;這個問題特別提出了爲什麼不要去做你在這裏描述的事情,而不是以另一種方式來解決它。何時使用'$ scope。$ apply();'查看@gaul的優秀答案。 – PureSpider

+0

雖然沒有回答這個問題:''''超時'''是關鍵!它工作,後來我發現它也是推薦。 –

+0

我知道在2年後添加評論已經相當晚了,但是在使用$ timeout的時候要小心,因爲如果你沒有很好的應用程序結構,這會在性能上花費太多 – cpoDesign

1

使用$timeout,這是建議的方式。

我的方案是我需要根據從WebSocket收到的數據更改頁面上的項目。而且由於它在Angular之外,沒有$超時,唯一的模型將被改變,但不是視圖。因爲Angular不知道這部分數據已經改變。 $timeout基本上是告訴Angular在下一輪美元消化中做出改變。

我也試過以下,它的工作原理。與我不同的是$超時更清晰。

setTimeout(function(){ 
    $scope.$apply(function(){ 
     // changes 
    }); 
},0) 
+0

將套接字代碼封裝在$ apply中非常簡潔(就像AJAX代碼中的Angular's一樣,即$ http')。否則,你必須在整個地方重複這些代碼。 – timruffles

+0

這絕對不推薦。另外,如果$ scope有$$階段,你偶爾會得到一個錯誤。相反,你應該使用$ scope。$ evalAsync(); – FlavorScape

+0

如果您使用'setTimeout'或'$ timeout',則不需要'$ scope。$ apply' – Kunal

11

現在肯定是反模式。即使你檢查$$階段,我也看到了一個消化爆炸案。你只是不應該訪問由$$前綴表示的內部API。

應使用

$scope.$evalAsync(); 

,因爲這是在角^ 1.4優選的方法和具體公開爲用於應用層的API。

-1

我覺得很冷靜的解決方案:

.factory('safeApply', [function($rootScope) { 
    return function($scope, fn) { 
     var phase = $scope.$root.$$phase; 
     if (phase == '$apply' || phase == '$digest') { 
      if (fn) { 
       $scope.$eval(fn); 
      } 
     } else { 
      if (fn) { 
       $scope.$apply(fn); 
      } else { 
       $scope.$apply(); 
      } 
     } 
    } 
}]) 

注入的是,你需要:

.controller('MyCtrl', ['$scope', 'safeApply', 
    function($scope, safeApply) { 
     safeApply($scope); // no function passed in 
     safeApply($scope, function() { // passing a function in 
     }); 
    } 
]) 
相關問題