Nice programing

AngularJS : $ scope. $ apply () 호출시 이미 진행중인 $ digest 오류 방지

nicepro 2020. 9. 28. 10:08
반응형

AngularJS : $ scope. $ apply () 호출시 이미 진행중인 $ digest 오류 방지


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

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

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

누구 든지이 오류를 피하거나 동일한 것을 달성하는 방법을 알고 있습니까?


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

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

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

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

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

누구든지 알고 있다면 더 나은 방법을 알고 싶습니다.


댓글에서 : @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지연이 설정되지 않은 경우 기본값이 0 인 $ browser.defer호출합니다 ).

조금 비 직관적이지만 Angular를 작성하는 사람들의 대답이므로 나에게 충분합니다!


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

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

또는 lodash가있는 경우 :

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

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


여기에있는 많은 답변에는 좋은 조언이 포함되어 있지만 혼란을 초래할 수도 있습니다. 단순히 사용하는 $timeout것은 최선이나 올바른 해결책 아닙니다 . 또한 성능이나 확장 성이 우려되는 경우 반드시 읽어보십시오.

알아야 할 사항

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

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

  • $timeout(callback, delay, false)동일한 작업을 수행하지만 (콜백을 실행하기 전에 선택적 지연 포함 $apply) Angular 모델 ($ 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);
}

참조 http://docs.angularjs.org/error/$rootScope:inprog를

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

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

이 오류를 방지하는 방법은 호출하는 코드가 $apply비동기 적으로 실행되도록하는 것입니다. 이는 $timeout지연을 0(기본값)로 설정하여 호출 내에서 코드를 실행하여 수행 할 수 있습니다 . 그러나 내부에서 코드를 호출하면를 호출 할 $timeout필요가 없습니다. $apply$ timeout이 $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.


예를 들어 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();

  }]
)

이 오류가 발생하면 기본적으로 이미 뷰를 업데이트하는 중임을 의미합니다. $apply()컨트롤러 내 에서 호출 할 필요가 없습니다 . 뷰가 예상대로 업데이트되지 않고을 호출 한 후이 오류가 발생 $apply()하면 모델을 올바르게 업데이트하지 않았 음을 의미합니다. 세부 사항을 게시하면 핵심 문제를 파악할 수 있습니다.


가장 짧은 형태의 금고 $apply는 다음과 같습니다.

$timeout(angular.noop)

evalAsync를 사용할 수도 있습니다. 다이제스트가 완료된 후 언젠가 실행됩니다!

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

우선 이렇게 고치지 마세요

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

$ phase는 $ digest주기에 대한 부울 플래그 일 뿐이므로 $ apply ()가 실행되지 않는 경우가 있습니다. 그리고 그것은 나쁜 습관임을 기억하십시오.

대신 $timeout

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

밑줄이나 lodash를 사용하는 경우 defer ()를 사용할 수 있습니다.

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

이 방법을 사용하면 오류가 발생하는 경우가 있습니다 ( https://stackoverflow.com/a/12859093/801426 ).

이 시도:

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

컨텍스트에 따라 $ evalAsync 또는 $ timeout을 사용해야합니다.

이것은 좋은 설명이있는 링크입니다.

http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm


다이제스트주기를 트리거하는 대신 사용자 지정 이벤트를 사용하는 것이 좋습니다.

사용자 지정 이벤트를 브로드 캐스팅하고이 이벤트에 대한 리스너를 등록하는 것이 다이제스트주기에 관계없이 발생하려는 작업을 트리거하는 데 좋은 솔루션이라는 것을 알게되었습니다.

사용자 지정 이벤트를 생성하면 해당 이벤트에 구독 된 리스너 만 트리거하고 scope. $ apply를 호출 한 경우처럼 범위에 바인딩 된 모든 감시를 트리거하지 않기 때문에 코드를 더 효율적으로 사용할 수 있습니다.

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


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

사용해보십시오

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

대신에

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

$ applyAsync 나중에 발생하도록 $ apply 호출을 예약합니다. 동일한 다이제스트에서 평가해야하는 여러 표현식을 대기열에 넣는 데 사용할 수 있습니다.

참고 : $ digest 내에서 $ applyAsync ()는 현재 범위가 $ rootScope 인 경우에만 플러시됩니다. 즉, 자식 범위에서 $ digest를 호출하면 $ applyAsync () 큐를 암시 적으로 플러시하지 않습니다.

예 :

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

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

참조 :

1. AngularJS 1.3의 Scope. $ applyAsync () 대 Scope. $ evalAsync ()

  1. AngularJs 문서

yearofmoo는 재사용 가능한 $ safeApply 함수를 만드는 데 큰 역할을했습니다.

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

사용법 :

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

함수가 실행될 것으로 알고있는 곳에서 $eval대신 호출하여이 문제를 해결할 수있었습니다 .$apply$digest

According to the docs, $apply basically does this:

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

In my case, an ng-click changes a variable within a scope, and a $watch on that variable changes other variables which have to be $applied. This last step causes the error "digest already in progress".

By replacing $apply with $eval inside the watch expression the scope variables get updated as expected.

Therefore, it appears that if digest is going to be running anyways because of some other change within Angular, $eval'ing is all you need to do.


use $scope.$$phase || $scope.$apply(); instead


Understanding that the Angular documents call checking the $$phase an anti-pattern, I tried to get $timeout and _.defer to work.

The timeout and deferred methods create a flash of unparsed {{myVar}} content in the dom like a FOUT. For me this was not acceptable. It leaves me without much to be told dogmatically that something is a hack, and not have a suitable alternative.

The only thing that works every time is:

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

I don't understand the danger of this method, or why it's described as a hack by people in the comments and the angular team. The command seems precise and easy to read:

"Do the digest unless one is already happening"

In CoffeeScript it's even prettier:

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

What's the issue with this? Is there an alternative that won't create a FOUT? $safeApply looks fine but uses the $$phase inspection method, too.


This is my utils service:

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

and this is an example for it's usage:

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

I have been using this method and it seems to work perfectly fine. This just waits for the time the cycle has finished and then triggers apply(). Simply call the function apply(<your scope>) from anywhere you want.

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

When I disabled debugger , the error is not happening anymore. In my case, it was because of debugger stopping the code execution.


similar to answers above but this has worked faithfully for me... in a service add:

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

You can use

$timeout

to prevent the error.

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

The issue is basically coming when, we are requesting to angular to run the digest cycle even though its in process which is creating issue to angular to understanding. consequence exception in console.
1. It does not have any sense to call scope.$apply() inside the $timeout function because internally it does the same.
2. The code goes with vanilla JavaScript function because its native not angular angular defined i.e. setTimeout
3. To do that you can make use of

if(!scope.$$phase){
scope.$evalAsync(function(){

}); }


Found this: https://coderwall.com/p/ngisma where Nathan Walker (near bottom of page) suggests a decorator in $rootScope to create func 'safeApply', code:

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

This will be solve your problem:

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

참고URL : https://stackoverflow.com/questions/12729122/angularjs-prevent-error-digest-already-in-progress-when-calling-scope-apply

반응형