[angularjs] AngularJS : $ scope. $ apply ()를 호출 할 때 $ digest 오류가 이미 발생하지 않도록 방지

각도로 응용 프로그램을 구축 한 후 페이지를 내 범위로 수동으로 업데이트해야한다는 것을 알게되었습니다.

내가 아는 유일한 방법 $apply()은 내 컨트롤러 및 지시문의 범위에서 호출 하는 것입니다. 이것의 문제는 콘솔에 다음과 같은 오류가 계속 발생한다는 것입니다.

오류 : $ digest가 이미 진행 중입니다

누구 든지이 오류를 피하거나 같은 것을 달성하지만 다른 방식으로 달성하는 방법을 알고 있습니까?



답변

이 패턴을 사용하지 마십시오. 이로 인해 해결되는 것보다 더 많은 오류가 발생합니다. 비록 당신이 무언가를 고쳤다 고 생각했지만 그렇지 않았습니다.

를 확인하여 a $digest가 이미 진행 중인지 확인할 수 있습니다 $scope.$$phase.

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

$scope.$$phase반환 "$digest"또는 "$apply"경우 $digest또는 $apply진행 중입니다. 이 주들 사이의 차이점 $digest은 현재 범위와 그 하위의 시계를 $apply처리하고 모든 범위의 감시자를 처리한다는 것입니다.

@ dnc253의 요점으로, 전화를 $digest하거나 $apply자주 전화를 하면 잘못하고있을 수 있습니다. 나는 일반적으로 Angular의 범위 밖에서 DOM 이벤트가 발생하여 스코프의 상태를 업데이트해야 할 때 요약해야합니다. 예를 들어 트위터 부트 스트랩 모달이 숨겨지는 경우. 때로는 $digest진행 중일 때 DOM 이벤트가 발생 하지만 때로는 그렇지 않습니다. 그래서이 검사를 사용합니다.

누군가 알고 있다면 더 나은 방법을 알고 싶습니다.


의견에서 : @anddoutoi

angular.js 안티 패턴

  1. 하지 마십시오 . 호출 스택에서 충분히 높지 않다는 if (!$scope.$$phase) $scope.$apply()것을 의미합니다 $scope.$apply().

답변

이 주제에 대한 Angular 사람들과의 최근 토론에서 : 미래 보장을 위해, 당신은 사용해서는 안됩니다$$phase

“올바른”방법으로 눌렀을 때 답은 현재

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

나는 최근에 콜백을받은 페이스 북, 구글 및 트위터 API를 포장하기 위해 각도 서비스를 작성할 때이 문제에 부딪쳤다.

다음은 서비스 내에서의 예입니다. (간단하게하기 위해 변수를 설정하고 $ timeout을 주입하는 등의 나머지 서비스는 중단되었습니다.)

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);
            }
        });
    });
});

$ timeout에 대한 지연 인수는 선택 사항이며 설정되지 않은 경우 기본값은 0입니다 ( $ timeout$ browser.defer 를 호출 하고 지연이 설정되지 않은 경우 기본값은 0 임)

조금 직관적이지 않지만 Angular를 작성하는 사람들의 대답이므로 충분합니다.


답변

다이제스트주기는 동기식 호출입니다. 브라우저 이벤트 루프가 완료 될 때까지 제어하지 않습니다. 이를 처리하는 몇 가지 방법이 있습니다. 이를 처리하는 가장 쉬운 방법은 내장 $ timeout을 사용하는 것이고, 두 번째 방법은 밑줄이나 lodash를 사용하는 경우 다음을 호출하는 것입니다.

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

또는 lodash가있는 경우 :

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

우리는 몇 가지 해결 방법을 시도했으며 모든 컨트롤러, 지시문 및 일부 공장에 $ rootScope를 주입하는 것을 싫어했습니다. 따라서 $ timeout과 _.defer는 지금까지 우리가 가장 좋아했습니다. 이 메소드는 다음 애니메이션 루프까지 기다릴 것을 각도에 성공적으로 지시하여 현재 범위를 적용합니다. $ apply가 끝났습니다.


답변

여기에 나오는 많은 답변에는 좋은 조언이 들어 있지만 혼란을 초래할 수도 있습니다. 간단하게 사용이 $timeout입니다 하지 최고의이나 적합한 솔루션. 또한 성능이나 확장 성이 염려되는 경우 반드시 읽어보십시오.

알아야 할 것들

  • $$phase 프레임 워크에 대해 비공개이며 이에 대한 충분한 이유가 있습니다.

  • $timeout(callback)현재 다이제스트 사이클 (있는 경우)이 완료 될 때까지 기다린 다음 콜백을 실행 한 다음 끝까지 실행합니다 $apply.

  • $timeout(callback, delay, false)콜백을 실행하기 전에 선택적 지연과 함께 동일한 작업을 수행하지만 $applyAngular 모델 ($ scope)을 수정하지 않은 경우 성능을 저장 하는 (세 번째 인수)를 발생시키지 않습니다.

  • $scope.$apply(callback)는 무엇보다도 호출합니다. $rootScope.$digest즉, 격리 된 범위 내에 있더라도 응용 프로그램 및 모든 해당 자식의 루트 범위를 다시 정의합니다.

  • $scope.$digest()단순히 모델을 뷰에 동기화하지만 부모 범위를 소화하지 않으므로 HTML의 고립 된 부분을 고립 된 범위 (주로 지시문)로 작업 할 때 많은 성능을 저장할 수 있습니다. $ digest는 콜백을받지 않습니다 : 코드를 실행 한 다음 요약하십시오.

  • $scope.$evalAsync(callback)angularjs 1.2와 함께 도입되었으며 아마도 대부분의 문제를 해결할 것입니다. 이에 대한 자세한 내용은 마지막 단락을 참조하십시오.

  • 을 얻는다면 $digest already in progress error아키텍처가 잘못되었습니다. 스코프를 다시 지정할 필요가 없거나 담당하지 않아야합니다. (아래 참조).

코드를 구성하는 방법

해당 오류가 발생하면 스코프가 이미 진행중인 동안 스코프를 다이제스트하려고합니다.이 시점에서 스코프의 상태를 알 수 없으므로 해당 다이제스트를 처리 할 책임이 없습니다.

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);
});

그리고 큰 Angular 응용 프로그램의 일부인 동안 격리 된 작은 지시문에서 수행중인 작업을 알고 있다면 성능을 절약하기 위해 $ apply 대신 $ digest를 선호 할 수 있습니다.

Angularjs 1.2 이후 업데이트

$ scope에 새롭고 강력한 방법이 추가되었습니다. $evalAsync . 기본적으로 현재 다이제스트 사이클이 발생하면 콜백을 실행합니다. 그렇지 않으면 새 다이제스트 사이클이 콜백을 실행하기 시작합니다.

$scope.$digestHTML의 분리 된 부분 만 동기화해야한다는 것을 실제로 알고 있다면 여전히 좋지는 않지만 ( $apply아무 것도 진행되지 않으면 새로운 것이 트리거 되기 때문에 ) 이것은 함수를 실행할 때 가장 좋은 솔루션입니다 어떤 동기 여부를 실행한다면 당신은 그것을 알 수 없다 때때로이 서버에 비동기 호출이 필요합니다 그렇지 않으면 자원이 동 기적으로 로컬로 가져온 것입니다 : 잠재적으로 캐시 된 자원을 가져 오는 한 후 예를 들어,.

이러한 경우 및 귀하가있는 다른 모든 경우, !$scope.$$phase사용하십시오$scope.$evalAsync( callback )


답변

이 프로세스를 유지하는 편리한 작은 도우미 방법

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


답변

예를 들어 CodeMirror와 같은 타사 스크립트와 Krpano와 같은 문제가 있었고 여기에 언급 된 safeApply 메소드를 사용해도 오류가 해결되지 않았습니다.

그러나 해결 한 것은 $ timeout 서비스를 사용하는 것입니다 (먼저 주입하는 것을 잊지 마십시오).

따라서 다음과 같은 것이 있습니다.

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

코드 내부에서 사용중인 경우

아마도 팩토리 지시자의 컨트롤러 안에 있거나 바인딩이 필요하기 때문에 다음과 같이 할 것입니다.

.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();

  }]
)


답변

http://docs.angularjs.org/error/$rootScope:inprog를 참조 하십시오

문제는 호출 $apply이 때때로 Angular 코드 외부에서 비동기 적으로 실행되고 ($ apply를 사용해야 할 때) 때로는 Angular 코드 내에서 동기식으로 실행될 때 발생합니다 ($digest already in progress 오류 경우에 발생합니다.

예를 들어 서버에서 항목을 비동기식으로 가져 와서 캐시하는 라이브러리가있는 경우 이런 상황이 발생할 수 있습니다. 항목이 처음 요청되면 코드 실행을 차단하지 않도록 비동기식으로 검색됩니다. 그러나 두 번째로 항목은 이미 캐시에 있으므로 동 기적으로 검색 할 수 있습니다.

이 오류를 방지하는 방법은 호출하는 코드가 $apply비동기 적으로 실행되도록하는 것입니다. $timeout지연 시간을 0기본값으로 설정하여 호출 내부에서 코드를 실행하면 됩니다 . 그러나 $ timeout은 자체적으로 다른 주기를 트리거 하여 필요한 모든 업데이트 등을 수행 하기 때문에 내부에서 코드 $timeout를 호출 $apply하면 을 호출 할 필요가 없습니다 $digest.

해결책

간단히 말해서, 이것을하는 대신 :

... your controller code...

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

... more of your controller code...

이 작업을 수행:

... your controller code...

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

... more of your controller code...

전화 만 $apply실행중인 코드를 알고있는 경우 하면 항상 Angular 코드 외부에서 실행됩니다 (예 : $ apply 호출은 Angular 코드 외부의 코드에 의해 호출되는 콜백 내에서 발생 함).

누군가 $timeoutover 사용에 대한 몇 가지 강력한 단점을 알고 $apply있지 않으면 거의 같은 일을 하기 때문에 왜 $timeout대신 대신 (제로 지연으로) 사용할 수 없었는지 알 수 없습니다 $apply.