[angularjs] 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로 노출되기 때문입니다.


답변

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

첫 번째

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

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

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

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

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


답변

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 개 이상의 속성에 대한 주기를 트리거 할 수 있습니다 !


답변

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

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

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

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


답변

매우 멋진 해결책을 찾았습니다.

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


답변