Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Angular.js의 $apply already in progress 오류

Angular.js는 양방향 바인딩을 지원하기 때문에 컨트롤러나 디렉티브에서 $scope의 값을 변경해 주면 뷰에서도 변경이 된다. 하지만 이 양방향 바인딩은 Angular.js가 스코프를 인지하고 있을 때만 지원하고 jQuery의 이벤트 리스너 등에서는 스코프를 인지하지 못하므로 scope의 값을 바꾸어도 뷰가 달라지지 않는다.

elem.click(function() {
  $scope.test = true;
});

예를 들어 디렉티브에서 위와 같이 코드로 이벤트 리스너를 추가하더라도 스코프의 값은 달라져도 뷰는 바뀌지 않는다.

elem.click(function() {
  $scope.$apply(function() {
    $scope.test = true;
  });
});

그래서 이런 경우는 위처럼 $apply를 사용해야 제대로 적용이 된다.

하지만 이렇게 사용해서 코딩을 했을 때 컨트롤러와 디렉티브가 복잡하게 얽혀있다 보니 둘의 $apply가 충돌하는 상황이 발생했다. 어떤 액션이 일어나며서 컨트롤러에서 스코프를 제어하고 있고 컨트롤러에서 발생시킨 동작이 디렉티브까지 이어져서 디렉티브에서도 계속 스코프를 변경하게 된 것이다.(예제를 만들어 볼까 했는데 너무 복잡해 져서..) 그러자 "$apply already in progress"라는 예외가 발생했다. 컨트롤러에서 현재 $apply가 동작중인데 디렉티브에서 다시 $apply를 사용하니까 충돌이 난 것이다.

elem.click(function() {
  if ($scope.$$phase == '$apply' || $scope.$$phase == '$digest' ) {
    $scope.test = true;
  } else {
    $scope.$apply(function() {
      $scope.test = true;
    });
  }
});

이는 위와 같이 해결할 수 있다. $$phase로 현재 스코프가 어느 단계에 있는지 알 수 있는데 $$phase$apply$digest이면 그냥 스코프를 변경하고 그렇지 않으면 $apply함수를 사용하도록 한 것이다. 이 부분에 관련해서 Angular.js에서 논의도 오간 내용이 있는데 이 논의에 나온 것처럼 safeApply()같은 함수를 따로 만들어서 사용해도 좋을 것이다.

빨리 Angular.js도 내부 소스를 보기 시작해야 이런 이슈를 쉽게 해결할텐데....

2013/09/16 23:32 2013/09/16 23:32