본문 바로가기

AngularJS

13. $scope -1 ($scope의 계층구조 및 $scope 타입)

1. $scope

 - $scope 는 양방향 데이터 바인딩의 핵심이자 뷰와 컨트롤러를 이어주는 징검다리이다.

 사실 $scope는 그저 단순한 자바스크립트 객체에 불과하다 하지만 이 자바스크립트 객체는 연결된 DOM 요소에서 표현식이 계산되는 실행환경이며

 뷰와 컨트롤러에서 사용되는 데이터(data)와 기능(function)이 살아 숨쉬는 공간이다.

또한 $scope는 위에서 보듯이 계층적 구조를 가진다.


 1) $scope 특징 

  뷰와 컨트롤러를 이어주는 다리

  연결된 DOM에서의 실행환경

  양방향 데이터 바인딩 처리

  이벤트 전파 처리

  계층적 구조


 2) $scope와 컨트롤러의 관계

 ① AngularJS의 컨트롤러는 하나의 컨트롤러에 하나의 $scope만을 가지게 된다.

 ② 컨트롤러 함수 두 개가 있을 경우 컨트롤러 함수당 별도의 $scope 객체가 생성된다.

 ③ 그리고 AngularJS 어플리케이션 루트에 해당하는 $rootScope가 있다.

 ④ 하나의 화면에 여러 컨트롤러를 사용하면 컨트롤러 별로 독립된 $scope가 생성되는데 각 독립된 $scope는 서로 참조할 수 없다.

 ⑤ 그래서 컨트롤러 사이에 데이터를 공유해야 할 경우 서비스를 이용한다.

 

 3) $scope의 계층구조

 - 모든 AngularJS 어플리케이션은 하나의 $rootscope를 가진다. 

 - $rootscope는 ng-app을 생성하며 ng-app이 선언된 DOM 요소가 최상위 노드가 되어 여러 자식 $socpe를 가지게 된다.

   즉, DOM과 같은 계층적 구조에서 최상위 계층에 $rootScope가 존재하는 것이다. 이는 어쩌면 window와 같은 글로벌 변수 영역이라고 생각할 수도 있다.

   또한, ng-controller나 ng-repeat과 같이 별도의 $scope를 생성하는 지시자는 각 지역변수 영역을 가지고 있다고 생각할 수 있다.

<!DOCTYPE html>
<html ng-app='app'>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="js/lib/angular.js" ></script>
<script type="text/javascript">
angular.module('app', [])
.controller('parentCtrl', function($scope){
$scope.parent={name: "parent Kim"};
})
.controller('childCtrl', function($scope){
$scope.child={name : "child Ko"};
$scope.changeParentName=function(){
$scope.parent.name="another kim";
};
});
</script>
</head>
<body>
<div ng-controller="parentCtrl">
<h1>부모이름 : {{parent.name}}</h1>
<div ng-controller="childCtrl" style="padding-left:20px;">
<h2>부모이름 : {{parent.name}}</h2>
<h2>자식이름 : {{child.name}}</h2>
<button ng-click="changeParentName()">부모이름 변경</button>
</div>
</div>

</body>
</html>

- 위 코드의 <html> 태그에 ng-app을 사용하면 $rootScope가 하나 만들어진다.

또한 <div ng-controller="parentCtrl">를 통해 $scope가 만들어지고 <div ng-controller="childCtrl">를 통해 또 하나의 $scope가 만들어졌다.

총 세 개의 $scope가 만들어 진것이다.

- 위 코드를 실행하면 childCtrl의 $scope가 parentCtrl의 $scope의 모델에 접근을 하며 값까지 변경을 하는데 이는 부모 $scope로부터 프로토타입을 상속 받기 때문에 가능한 것이다.

즉, 자식 #scope에서 없는 모델 즉, 속성을 부모 $scope에서 찾는다. 



                                   

    <$scope의 프로토타입>


4) Scope 타입

 - $scope객체나 $rootScope 객체는 AngularJS내부에서 정의하는 Scope 타입의 인스턴스다. 

즉 다음과 같이 별도의 생성자 함수가 AngularJS 내부에 정의돼 있다.

function Scope(){...}

Scope.prototype.$apply = function(){};

Scope.prototype.$digest = function(){};

Scope.prototype.$watch = function(){};

Scope.prototype.$new = function(){};

 - AngularJS는 초기 부트스트랩 시 프레임워크 내부에서 $rootScope을 new Scope()와 같이 생성한 후 해당 $rootScope을 서비스로 제공한다. 그리고 ng-controller나 웹 어프리케이션

에서는 다음과 같이 $rootscope을 이용해 자식 $scope 객체들을 만들 수 있다.

var $scope = $rootScope.$new();

◈ scope 타입의 프로토타입 메서드

 ① apply(표현식 혹은 함수) : 주로 외부 환경에서 AngularJS 표현식을 실행할 때 사용한다. 즉, 외부 라이브러리로 이벤트를 처리할 때나 setTimeout 메서드를 사용할 때 사용한다.

인자로는 표현식이나 함수를 전달할 수 있다. 표현식을 전달하면 해당 표현식을 계산하고 함수를 전달하면 함수를 실행시키다.

그리고 내부적으로 $rootScope의 $digest를 실행해 등록된 모든 $watch를 실행하게 된다.


 ② $broadcast(이벤트 이름, 인자들...) : 첫 번째 인자인 이벤트 이름으로 하는 이벤트를 모든 하위 $scope에게 발생시킨다.

가령 $scope.$broadcast('popup : open', {title : "hello"}); 를 호출하면 $on 메서드를 이용해 해당 이벤트 (popup:open)을 듣고 있는 $scope들에게 {title : "hello"}의 데이터를 전달할 수 있다. 잘 활용하면 $scope들 사이의 참조 관계를 매우 느슨하게 만들어 재활용할 수 있는 UI 컴포넌트 개발에 용이하다.

 ③ $destroy() : 현재 $scope를 제거할 수 있다. 또한, 모든 자식 $scope까지 파괴한다.


 ④ $digest() ; $scope와 그 자식에 등록된 모든 $watch 리스너 함수를 실행시킨다. $watch 리스너 함수가 보는 표현식에 대하여 변화가 없다면 리스너 함수는 실행시키지 않는다.


 ⑤ $emit(이벤트명, 인자들...) : 해당 $scope를 기준으로 상위 계층 $scope에게 이벤트 명으로 인자를 전달한다. 물론 $on으로 이벤트 명을 듣고 있는 상위 계층에 한하여 전파한다.


 ⑥ $eval(표현식, 로케일) : 주어진 표현식을 계산하고 그 결과를 반환한다. 물론 현재 $scope를 기준으로 표현식이 계산된다. 


 ⑦ $evalAsync(표현식) : $eval과 마찬가지나, 표현식의 결과값이 바로 반환되지 않고 나중에 어떠한 시점에서 그 결과가 반환된다. 하지만 적어도 한번의 $digest가 호출된다.


 ⑧ $new(독립여부) : 새로운 자식 $scope를 생성한다. 독립여부를 true, false로 전달하는 데 true일 경우 프로토타입을 기반으로 상속하지 않게 된다.


 ⑨ $on(이벤트 이름, 리스터 함수) : 주어진 이벤트 이름으로 이벤트를 감지하다가 해당 이벤트가 발생하면 리스너 함수를 실행한다. 이벤트 리스너 함수는 첫번째 인자로 이벤트 객체

를 받고 다음으로 $emit이나 $broadcast에서 전달한 값을 인자로 받는다.


 ⑩ $watch(표현식, 리스너 함수, 동등성 여부) : 대상 $scope에 특정 표현식을 감지하는 리스너 함수를 등록한다. 가령 $scope의 data 속성에 특정 객체가 할당되어 있다고 하자.

그리고 $scope.$watch("data", function(){...})로 함수를 호출하면 $scope.data의 레퍼런스가 변경될 때 리스너 함수가 호출된다. 

리스터 함수에는 인자로 새로운 값과 이전 값이 주어진다. 동등성 여부는 변경을 레퍼런스로 감지할 것인지 동등한 여부로 감지할 것인지를 정할 때 사용된다.

기본값은 false이며 레퍼런스 변경시에만 리스너 함수가 호출된다.


 ⑪ $watchCollection(표현식, 리스너 함수) : 기본적으로 $watch와 같은 기능을 하며 대신 배열이나 객체에 대한 변경을 감지할 때 사용한다.

배열일 경우 새로운 배열 요소가 추가되거나 배열 요소들 사이의 순서가 변경되거나 배열요소가 삭제될 때마다ㅏ 리스너 함수가 호추로딘다. 

객체일 경우 속성에 변경이 있을 때마다 리스너 함수가 호출된다.


◈ 위 함수를 사용 시점별로 묶으면...

 ① 데이터 바인딩 처리 시 : $apply, $digest, $watch, $watchCollection

 ② 사용자 정의 이벤트 처리 시 : $broadcast, $emit, $on

 ③ 표현식을 $scope 객체의 컨텍스트(context)에서 계산할 때 사용 시 : $eval과 $evalAsync

 ④ $scope의 생성과 파괴 처리 시 : $new, $destroy

- $scope객체는 scope 타입의 인스턴스이므로 프로토타입 상속에 의해 위 메서드를 사용할 수 있다.