if (! $ scope. $$ phase) $ scope. $ apply ()를 안티 패턴으로 사용하는 이유는 무엇입니까?


92

때때로 $scope.$apply내 코드에서 사용해야 하며 때로는 "다이제스트 진행 중"오류가 발생합니다. 그래서 저는이 문제를 해결하기 시작 했고이 질문을 발견했습니다 : AngularJS : $ scope. $ apply ()를 호출 할 때 이미 진행중인 오류 $ digest 방지 . 그러나 주석 (및 각도 위키)에서 다음을 읽을 수 있습니다.

If (! $ scope. $$ phase) $ scope. $ apply ()는 $ scope. $ apply ()가 호출 스택에서 충분히 높지 않다는 것을 의미합니다.

이제 두 가지 질문이 있습니다.

  1. 이것이 정확히 안티 패턴 인 이유는 무엇입니까?
  2. $ scope. $ apply를 안전하게 사용하려면 어떻게해야합니까?

"다이제스트가 이미 진행 중"오류를 방지하는 또 다른 "솔루션"은 $ timeout을 사용하는 것 같습니다.

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

그게 갈 길 이니? 더 안전한가요? 그래서 여기 진짜 질문이 있습니다. "이미 진행중인 다이제스트"오류의 가능성을 완전히 제거 할 수 있는 방법은 무엇입니까?

추신 : 동기가 아닌 비 angularjs 콜백에서만 $ scope. $ apply를 사용하고 있습니다. (내가 아는 한 변경 사항을 적용하려면 $ scope. $ apply를 사용해야하는 상황입니다)


내 경험에 비추어 볼 때 scope각도 내부에서 조작하는지 각도 외부에서 조작하는지 항상 알아야 합니다. 따라서 이것에 따르면 전화가 필요한지 여부를 항상 알고 있습니다 scope.$apply. 둘 다 각도 / 비 각도에 동일한 코드를 사용하는 경우와 scope조작, 당신이 항상 기본적으로 당신이 확인해야 할 경우가 생기면 ... 분리되어야한다, 잘못을하고있는 scope.$$phase, 코드가없는 올바른 방법으로 설계하고, '올바른 방법'을 할 수있는 방법은 항상있다
doodeec

1
난 단지가 아닌 각 콜백이 사용하고 난 혼란 스러워요 이유는 (!)
도미니크 Goltermann을

2
이 비 각도 있다면, 그것을 던져하지 않을 digest already in progress오류
doodeec

1
그것이 내가 생각했던 거죠. 문제는 항상 오류가 발생하는 것은 아닙니다. 가끔씩 만. 내 의심은 적용이 다른 다이제스트와 우연히 충돌하는 것입니다. 가능합니까?
도미니크 Goltermann

콜백 엄격 아닌 각 경우에 나는 가능하다 생각하지 않는다
doodeec

답변:


113

좀 더 파고 나니 항상 사용하기에 안전한지에 대한 질문을 해결할 수있었습니다 $scope.$apply. 짧은 대답은 '예'입니다.

긴 대답 :

브라우저가 자바 스크립트를 실행하는 방식으로 인해 두 개의 다이제스트 호출 이 우연히 충돌 하는 것은 불가능합니다 .

우리가 작성한 JavaScript 코드는 모두 한 번에 실행되는 것이 아니라 차례로 실행됩니다. 이러한 각 턴은 처음부터 끝까지 중단되지 않고 실행되며, 턴이 진행될 때 브라우저에서 다른 일이 발생하지 않습니다. ( http://jimhoskins.com/2012/12/17/angularjs-and-apply.html에서 )

따라서 "다이제스트가 이미 진행 중"이라는 오류는 다음과 같은 상황에서만 발생할 수 있습니다. $ apply가 다른 $ apply 내부에서 발행되는 경우, 예 :

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

이 상황은 우리가 순수한 비 angularjs 콜백에서 $ scope.apply를 사용 한다면 발생할 수 없습니다 . 따라서 다음 코드는 100 % 방탄이며 다음 을 수행 할 필요 가 없습니다 .setTimeoutif (!$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);
});

안전 하지 않은 것은 무엇입니까 ($ timeout-모든 angularjs 도우미처럼-이미 $scope.$apply당신을 호출 하기 때문입니다) :

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

이것은 또한의 사용이 if (!$scope.$$phase) $scope.$apply()안티 패턴 인 이유를 설명합니다 . $scope.$apply올바른 방법으로 사용하는 경우에는 필요하지 않습니다 setTimeout. 예를 들어 순수한 js 콜백에서 .

읽기 http://jimhoskins.com/2012/12/17/angularjs-and-apply.html을 더 자세한 설명.


나는이있는 서비스를 만들 예를 들어 가지고 $document.bind('keydown', function(e) { $rootScope.$apply(function() { // a passed through function from the controller gets executed here }); });내가 $을해야 여기에 적용하는 이유는 $ document.bind를 사용하고 있기 때문에 정말 모르겠어요 .. I를
베티 세인트

$ document는 "브라우저의 window.document 객체에 대한 jQuery 또는 jqLite 래퍼"일 뿐이 기 때문입니다. 다음과 같이 구현됩니다. function $DocumentProvider(){ this.$get = ['$window', function(window){ return jqLite(window.document); }]; }거기에 적용되지 않습니다.
Dominik Goltermann 2014 년

11
$timeout의미상은 지연 후 코드를 실행하는 것을 의미합니다. 기능적으로는 안전한 일이지만 해킹입니다. $digest사이클이 진행 중인지 또는 이미 .NET Framework 내부에 있는지 알 수없는 경우 $ apply를 사용하는 안전한 방법이 있어야합니다 $apply.
John Strickler 2014

1
나쁜 또 다른 이유는 공개 API의 일부가 아닌 내부 변수 ($$ phase)를 사용하며 최신 버전의 angular에서 변경되어 코드가 손상 될 수 있다는 것입니다. 트리거 syncronous 이벤트에 귀하의 문제는 흥미로운 생각입니다
도미니크 Goltermann

4
새로운 접근 방식은 가능한 경우 현재 다이제스트주기 또는 다음주기에서 안전하게 실행되는 $ scope. $ evalAsync ()를 사용하는 것입니다. bennadel.com/blog/를
jaymjarri

16

이제 가장 확실히 안티 패턴입니다. $$ 단계를 확인하더라도 다이제스트가 폭발하는 것을 보았습니다. $$접두사 로 표시된 내부 API에 액세스하면 안됩니다 .

당신은 사용해야합니다

 $scope.$evalAsync();

이것은 Angular ^ 1.4에서 선호되는 방법이며 특히 응용 프로그램 계층에 대한 API로 노출되기 때문입니다.


9

어떤 경우에도 다이제스트가 진행 중이고 다이제스트를 위해 다른 서비스를 푸시하면 단순히 다이제스트가 이미 진행 중이라는 오류가 발생합니다. 이를 치료하기 위해 두 가지 옵션이 있습니다. 폴링과 같이 진행중인 다른 다이제스트를 확인할 수 있습니다.

첫 번째

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

위의 조건이 참이면 $ scope. $ apply otherwies not and

두 번째 해결책 은 $ timeout을 사용하는 입니다.

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

$ timeout 실행이 완료 될 때까지 다른 다이제스트를 시작하지 않습니다.


1
반대 투표; 이 질문은 특히 당신이 여기에서 설명하는 것을하지 말아야하는 이유를 묻습니다. 언제 사용할지 @gaul의 훌륭한 답변을 참조하십시오 $scope.$apply();.
PureSpider 2014 년

질문에 대답하지 않지만 : $timeout열쇠입니다! 그것은 작동하고 나중에 나는 그것이 추천된다는 것을 알았습니다.
Himel Nag Rana

2 년 후이 부분에 주석을 추가하는 것은 꽤 늦었다는 것을 알고 있지만 $ timeout을 너무 많이 사용할 때는주의해야합니다. 응용 프로그램 구조가 좋지 않으면 성능에 너무 많은 비용이들 수 있습니다.
cpoDesign

9

scope.$apply$digest양방향 데이터 바인딩의 기본주기를 트리거합니다.

$digest객체주기 검사 모델 (정확하게는, 즉 $watch부착) $scope그 값이 변경되었는지를 평가하고 그 다음의 변화를 검출하면 그것은 뷰를 갱신하는 데 필요한 단계를 수행.

이제 사용할 때 $scope.$apply"이미 진행 중"이라는 오류가 발생 하므로 $ digest가 실행중인 것이 분명하지만 무엇이 트리거 되었습니까?

ans-> 모든 $http호출, 모든 ng-click, repeat, show, hide 등이 $digest주기를 트리거하며 가장 나쁜 부분은 모든 $ SCOPE에서 실행됩니다.

즉, 페이지에 4 개의 컨트롤러 또는 지시문 A, B, C, D가 있다고 가정합니다.

$scope각각에 4 개의 속성이있는 경우 페이지에 총 16 개의 $ scope 속성이 있습니다.

$scope.$apply컨트롤러 D에서 트리거 하면 $digest사이클이 16 개 값을 모두 확인합니다 !!! 그리고 모든 $ rootScope 속성을 포함합니다.

대답-> 하지만 자식 및 동일한 범위에 대해 $scope.$digest트리거 $digest하므로 4 개의 속성 만 확인합니다. 따라서 D의 변경 사항이 A, B, C에 영향을 미치지 않는다고 확신하는 경우 $scope.$digest not 을 사용하십시오 $scope.$apply.

따라서 단순히 ng-click 또는 ng-show / hide $digest는 사용자가 이벤트를 실행하지 않은 경우에도 100 개 이상의 속성에 대한 주기를 트리거 할 수 있습니다 !


2
예, 불행히도 프로젝트 후반부에 이것을 깨달았습니다. 처음부터 이것을 알았다면 Angular를 사용하지 않았을 것입니다. 모든 표준 지시문은 $ scope. $ apply를 실행하고, 차례로 $ rootScope. $ digest를 호출하여 모든 범위에 대해 더티 검사를 수행합니다. 저에게 물어 보면 잘못된 디자인 결정입니다. 데이터가 이러한 범위에 연결되는 방식을 알고 있기 때문에 어떤 범위를 더티 검사해야하는지 제어해야합니다!
MoonStom

0

사용 $timeout, 권장하는 방법입니다.

내 시나리오는 WebSocket에서받은 데이터를 기반으로 페이지의 항목을 변경해야한다는 것입니다. 그리고 그것은 Angular 외부에 있기 때문에 $ timeout없이 유일한 모델은 변경되지만 뷰는 변경되지 않습니다. Angular는 데이터가 변경되었음을 알지 못하기 때문입니다. $timeout기본적으로 Angular에게 $ digest의 다음 라운드에서 변경하도록 지시하는 것입니다.

나는 다음도 시도했고 작동합니다. 나에게 차이점은 $ timeout이 더 명확하다는 것입니다.

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

소켓 코드를 $ apply로 감싸는 것이 훨씬 더 깔끔합니다 (AJAX 코드의 Angular와 비슷 $http합니다. 그렇지 않으면이 코드를 모든 곳에서 반복해야합니다.
timruffles

이것은 확실히 권장되지 않습니다. 또한 $ scope에 $$ phase가있는 경우 가끔 오류가 발생합니다. 대신 $ scope. $ evalAsync ();를 사용해야합니다.
FlavorScape

의 필요가 없습니다 $scope.$apply당신이 사용하는 경우 setTimeout또는$timeout
쿠날을

-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
        });
    }
])
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.