Programing

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

lottogame 2020. 9. 3. 23:40
반응형

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


때때로 $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.$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을 더 자세한 설명.


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

당신은 사용해야합니다

 $scope.$evalAsync();

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


scope.$apply triggers a $digest cycle which is fundamental to 2-way data binding

A $digest cycle checks for objects i.e. models(to be precise $watch) attached to $scope to assess if their values have changed and if it detects a change then it takes necessary steps to update the view.

Now when you use $scope.$apply you face an error "Already in progress" so it is quite obvious that a $digest is running but what triggered it?

ans--> every $http calls, all ng-click, repeat, show, hide etc trigger a $digest cycle AND THE WORST PART IT RUNS OF EVERY $SCOPE.

ie say your page has 4 controllers or directives A,B,C,D

If you have 4 $scope properties in each of them then you have a total of 16 $scope properties on your page.

If you trigger $scope.$apply in controller D then a $digest cycle will check for all 16 values!!! plus all the $rootScope properties.

Answer-->but $scope.$digest triggers a $digest on child and same scope so it will check only 4 properties. So if you are sure that changes in D will not affect A, B, C then use $scope.$digest not $scope.$apply.

So a mere ng-click or ng-show/hide might be triggering a $digest cycle on over 100+ properties even when the user has not fired any event!


In any case when your digest in progress and you push another service to digest, it simply gives an error i.e. digest already in progress. so to cure this you have two option. you can check for anyother digest in progress like polling.

First one

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

if the above condition is true, then you can apply your $scope.$apply otherwies not and

second solution is use $timeout

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

it will not let the other digest to start untill $timeout complete it's execution.


Use $timeout, it is the way recommended.

My scenario is that I need to change items on the page based on the data I received from a WebSocket. And since it is outside of Angular, without the $timeout, the only model will be changed but not the view. Because Angular doesn't know that piece of data has been changed. $timeout is basically telling Angular to make the change in the next round of $digest.

I tried the following as well and it works. The difference to me is that $timeout is clearer.

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

I found very cool solution:

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

inject that where you need:

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

참고URL : https://stackoverflow.com/questions/22346990/why-is-using-ifscope-phase-scope-apply-an-anti-pattern

반응형