Angualr Scope란?

Scope란 무엇인가? 

Scope는 어플리케이션 모델을 참조하고 있는 객체를 말한다.
이것은 expression을 위한 컨텍스트를 실행하게 된다.
Scope는 DOM 구조와 닮은 계층적 구조로 나열되어 있다.
Scopes는 expressions와 이벤트 전이를 watch할 수 있다.

Scope의 특징 : 

Scopes는 $watch API를 제공하며 이는 모델의 변경을 감시한다.
Scopes는 $apply API를 제공하며, Angular왕국 (controllers, services, Angular event handlers)와 같은 외부로 부터 발생된 변경사항을 부로 반영한다.

Scope는 공유된 모델 속성에 접근을 제공하는 반면 어플리케이션 컴포넌트의 속성에 접근하기 위한 중첩된 접근 제한도 될 수 있다. 중첩된 scope는 "child scopes" 혹은 "isolate scopes"들이며, "child scope" 부모 스콥으로부터 속성을 상속 받는다. "isolate scope"는 그렇지 못하다.

Scope는 evaluated된 expression에 대한 컨텍스트를 제공한다. 예를 들어 {{username}}표현식은 username 속성이 정의된 특정 스콥에 대한 evaluate가 없다면 무의미한 것이된다.

Data-Model로서의 Scope : 

Scope는 어플리케이션의 컨트롤러와 view를 서로 접착하는 접착제이다. template 링킹 단계동안 directives는 scope에서 $watch 표현식을 설정한다. $watch는 속성의 변경에 대한 알림이 되도록 지시할 수 있다. 이것은 DOM 값을 업데이트 하게 된다.

컨트롤러와 디렉티브는 scope에 대한 참조를 가진다. 그러나 서로는 참조할수 없다. 이러한 고립정책은 DOM으로 부터 controller을 분리 시킨다.
이것은 매우 중요한 포인트로 컨트롤러는 뷰가 필요 없게 하고, 뷰는 컨트롤러가 필요없게 만들어 주어 어플리케이션 스토리의 테스트를 매우 향상 시켜준다.

------------------------------------------------------------------
angular.module('scopeExample', [])
.controller('MyController', ['$scope', function($scope) {
  $scope.username = 'World';

  $scope.sayHello = function() {
    $scope.greeting = 'Hello ' + $scope.username + '!';
  };
}]);
------------------------------------------------------------------


------------------------------------------------------------------
<div ng-controller="MyController">
  Your name:
    <input type="text" ng-model="username">
    <button ng-click='sayHello()'>greet</button>
  <hr>
  {{greeting}}
</div>
------------------------------------------------------------------

이 예제는 MyController에 'World'를 할당하고 scope의 username에 할당한다. scope는 할당된 입력값을 알리고, "Hello"와 결합하여 렌더링 한다. 이 예제는 어떻게 컨트롤러가 데이터를 scope에 쓸수 있는지 보여주는 좋은 예이다.

유사하게 controller는 sayHello메소드에서 본것과 같이 행위를 할당할 수 도 있다. 이것은 사용자가 "greet"를 클릭하는 경우 수행될 수 있도록 한다. sayHello메소드는 username속성을 읽고 greeting 속성을 생성한다. 이 예제는 scope 의 속성값을 자동적으로 업데이트 하도록 HTML입력 위젯에 바인딩 하는 내용을 보여준다.

{{greeing}}에 포함된 로직은 다음과 같다.
- 템플릿의 DOM노드에서 {{greeting}}으로 정의된 scope를 탐색한다. 이 예제에서는 MyController에서 scope에 지정되어 있다. 이 scope의 게층구조에 대해서는 이후에 설명하도록 하자.
- greeting표현식을 Evaluate한다. 이는 상단 스콥을 탐색하여, 결과값을 DOM엘리먼트에 반영한다.

scope내에 속성값에 데이터는 view에 렌더링 된다고 생각할 수 있다. scope는 모든 관련된 뷰에서singlesource-of-truth이다.

뷰의 관점에서 테스트를 위해서 컨트롤러의 분리는 매우 필요한 것이다. 왜냐하면 뷰에 집중하도록 하여 컨트롤러가 함께 있는 산만함을 없애준다.

------------------------------------------------------------------
it('should say hello', function() {
  var scopeMock = {};
  var cntl = new MyController(scopeMock);

  // Assert that username is pre-filled
  expect(scopeMock.username).toEqual('World');

  // Assert that we read new username and greet
  scopeMock.username = 'angular';
  scopeMock.sayHello();
  expect(scopeMock.greeting).toEqual('Hello angular!');
});
------------------------------------------------------------------

Scope Hierarchies

각 Angular 어플리케이션은 하나의  scope를 가진다. 그러나 몇개의 자식 scope들을 가질 것이다.
어플리케이션은 복수개의 스콥을 가질 수 있다. 왜냐하면 directives는 새로운 자식 scope를 생성하기 때문이다. (directive 는 새로운 스콥을 생성한다. ) 새로운 스콥이 생성되면, 그들의 부모 scope의 자식으로 추가된다. 이 생성트리는 DOM과 같이 병행하여 추가된다.

Angular가 {{name}}를 evaluate할때 name속성으로 주어진 엘리먼트와 연결된 scope를 우선 찾는다. 만약 properties가 존재하지 않는다면 부모 스콥을 찾게 되며, root에 다다를때까지 이러한 행위가 반복된다. JavaScript에서 이 행위는 prototypical inheritance로 알려져 있다. 자식의 스콥은 prototypically 상속을 자신의 부모로 부터 받는다.

다음 예제는 어플리케이션에서 scopes에 대한 일러스트를 보여준다. 그리고 프로퍼티의 prototypical inheritance 를 보여준다. 이 예제는 스콥의 영역을 보여주고 있다.


------------------------------------------------------------------
<div class="show-scope-demo">
  <div ng-controller="GreetController">
    Hello {{name}}!
  </div>
  <div ng-controller="ListController">
    <ol>
      <li ng-repeat="name in names">{{name}} from {{department}}</li>
    </ol>
  </div>
</div>
------------------------------------------------------------------


------------------------------------------------------------------
angular.module('scopeExample', [])
.controller('GreetController', ['$scope', '$rootScope', function($scope, $rootScope) {
  $scope.name = 'World';
  $rootScope.department = 'Angular';
}])
.controller('ListController', ['$scope', function($scope) {
  $scope.names = ['Igor', 'Misko', 'Vojta'];
}]);
------------------------------------------------------------------


------------------------------------------------------------------
.show-scope-demo.ng-scope,
.show-scope-demo .ng-scope  {
  border: 1px solid red;
  margin: 3px;
}
------------------------------------------------------------------

Hello World!
  1. Igor from Angular
  2. Misko from Angular
  3. Vojta from Angular

Angular는 자동적으로  ng-scope 클래스를 scope가 추가된 엘리먼트의 클래스에 위치시킨다. <style> 로 정의된 곳은 (붉은색 표시) 신규 scope가 위치된 영역이다. 이 자식 스콥들이 필요한 이유는 repeater가 {{name}}표현식을 검증하기 때문이다. 그러나 각 scope 표현은 서로다른 결과를 나타낸다. {{department}}의 검증은 root scope로 부터 prototypically inherit 되어진다. 이것은 오직 department 속성만 존재하기 때문이다.

DOM으로 부터 Scopes 순회하기 

Scopes는 DOM에 $scope 데이터 속성으로 추가된다. 그리고 디버깅 목적으로 순회하게 된다. rootScope는 DOM중에서 ng-app 디렉티브의 위치에 정의된다. 일반적으로 ng-app는 <html> 엘리먼트에 위치한다. 그러나 다른 엘리먼트에도 들어갈 수 있다. 예를 들면 Angular에 의해서한 부분만 제어되기를 원하는 경우가 그렇다.

디버거에서 scope 테스트 :
1. 브라우저에서 원하는 엘리먼트에서 오른쪽 클릭을 하여 'inspect element'를 선택한다. 그러면 브라우저 디버거가 열리고 원하는 위치가 하이라이트 된다.
2. 현재 선택된 엘리먼트에서 $0 값을 콘솔에 입력하여 디버거에 접근한다.
3. 콘솔에서 angular.element($0).scope()로 해당 $scope에 접근할 수 있다.

Scope Events Propagation

scope는 DOM 이벤트와 같은 방법으로 이벤트를 전파할 수 있다. 이벤트는 scope의 자식으로 broadcast 할수 있고, 자식에서 부모로 emitted 할 수 있다.
------------------------------------------------------------------
angular.module('eventExample', [])
.controller('EventController', ['$scope', function($scope) {
  $scope.count = 0;
  $scope.$on('MyEvent', function() {
    $scope.count++;
  });
}]);

------------------------------------------------------------------


------------------------------------------------------------------
<div ng-controller="EventController">
  Root scope <tt>MyEvent</tt> count: {{count}}
  <ul>
    <li ng-repeat="i in [1]" ng-controller="EventController">
      <button ng-click="$emit('MyEvent')">$emit('MyEvent')</button>
      <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>
      <br>
      Middle scope <tt>MyEvent</tt> count: {{count}}
      <ul>
        <li ng-repeat="item in [1, 2]" ng-controller="EventController">
          Leaf scope <tt>MyEvent</tt> count: {{count}}
        </li>
      </ul>
    </li>
  </ul>
</div>
------------------------------------------------------------------

Scope Life Cycle

일반적인 브라우저가 이벤트를 수신하는 플로우는 자바스크립트의 콜백에 대한 실행을 수행하는 것이다. 콜백이 완료되면 브라우저는 DOM을 다시 렌더링하고 추가적인 이벤트를 기다리기 위해 return한다. 

브라우저가 자바스크립트 코드를 호출할때 Angular 외부의 컨텍스트를 수행하면, 이것은 Angular가 모델의 변경사항을 감지하지 못한다는 의미이다. 모델의 변경을 수행하기 위해서는 $apply를 수행하여 Angular가 컨텍스트를 실행할 수 있도록 해주어야 한다. 오직 모델의 변경은 $apply 메소드 내부에서 수행된 경우에만 Angular에 의해서 처리될 수 있다. 예를 들어 DOM 이벤트를 리슨하는 디렉티브 ng-click와 같은 디렉티브는 $apply 메소드의 내부에서 evaluation이 수행되어야 한다. 

expression이 evaluating이 두행되고 난 뒤에는 $apply메소드는 $digest를 수행한다. $digest 단계에서scope는 $watch 익스프레션의 전체를 검증하고 이전 값과 비교를 수행한다. dirty checking은 비동기적으로 끝이난다. 이 의미는 $scope.username="angulat"과 같이 할당한 값이 $watch 에 의해서 즉시 노티되지 않는다는 것을 의미한다. 대신에 $watch 노티는 다음 $digest단계까지 딜레이 된다. 이 딜레이는 $watch가 수행되는 동안 다른 $watch가 수행되지 않는다는 것을 보장하는 것이다. 만약 $watch가 모델의 값을 변경하면 이것은 강제적으로 $digest사이클을 수행한다.

1. Creation : 
  root scope가 $injector에 의해서 어플리케이션 bootstrap 동안 생성된다. 템플릿 링킹동안 몇가지 디렉티브들은 새로운 child scope들을 생성한다. 

2. Watcher registration : 
  템플릿 링킹동안 디렉티브는 scope에 watches를 등록한다. 이 watches는 모델을 DOM으로 전파할때 이용된다. 

3. Model mutation : 
  모델 변경이 적합하게 관찰되기 위해서는 scope.$apply()에서 처리되어야한다. Angular API는 명시적으로 수행한다. 추가적으로 $apply는 controller에서는 동기적으로 수행되어야 하며, $http, $timeout혹은 $interval서비스에서는 비동기적으로 수행된다. 

4. Mutation ovservation : 
  $apply의 끝에서는 Angular는 $digest root scope에서 사이클을 수행한다. 이것은 모든 자식 scope에 전달된다. $digest 사이클동안 모든 $watch 표현 혹은 함수들은 모델의 변경에 대한 체크가 수행되며, 만약 변경이 발견되면 $watch listener는 호추된다. 

5. Scope destruction : 
  자식 scope가 더이상 필요하지 않은경우 자식 scope의 생성자는 삭제된다. 이것은 scope.$destroy() API를 통해 수행되며 이것은 $digest이 자식 scope를 호출하는 것을 멈추고 gabage collector에 의해서 재생되기를 기다린다. 

Scopes and Directives : 

compilation 단계동안 compiler는 DOM템플릿에 대한 디렉티브 매치를 수행한다. 디렉티브는 보통 2개의 카테고리에 속한다.
- Ovserving directives : {{ }} 과 같은 표현식으로, register listener는  $watch()메소드를 사용한다. 이 디렉티브 타입은 표현식이 변경되는 경우 노티가 수행되며 이것은 view를 업데이트 할 수 있게 된다.
- Listener directives : ng-click와 같은 것으로 등록된 리스너는 해당 이벤트가 fire된경우 연관된 표션식을 수행하고, $apply()메소드를 이용하여 뷰를 업데이트 한다.

외부 이벤트가 수신되면, (사용자 액션 혹은 XHR) 연관된 expression은 $apply메소드를 통해서 적용되어야 하며 이것은 모든 리스너가 정확히 업데이트 된다는 것을 의미한다.

Directives that Create Scopes 

대부분의 케이스에서 directive와 scope는 상호작용 한다. 그러나 scope의 새로운 인스턴스를 생성하지 않는다. 그러나 몇몇 directive는 ng-controller혹은 ng-repeat는 새로운 자식 scope를 생성하고, 이 자식 스코프를 DOM에 추가한다.
사용자는 angular.element(aDomElement).scope() 메소드를 이용하여 스콥을 순회할 수 있다.
directive guide를 참조하여 추가적인 정보를 획득하고, isolate scope에 대해서 확인하자.

Controllers and Scopes

Scopes와 Controllers 는 다음 상황에서 각각 상호작용 한다.
- Controller가 scope를 사용하여 컨트롤러 메소드를 템플릿에 노출하고자 하는경우
- Controller 정의 메소드가 Model을 변경하는 경우 (scope의 속성..)
- Controller가 모델에서 watches를 등록하는 경우, 이 watch는 컨트롤러가 특정행위를 수행하면 즉시 실행한다.

ng-controller파트를 추가로 확인하자.

Scope $watch Performance Considerations

특정 속성의 소콥에 대한 변경에 대해서 Dirty checking 은 Angular에서 공통적인 오퍼레이션이다. 이것은 dirty checking은 효과적으로 수행되어야 함을 의미한다. dirty checking함수는 어떠한 DOM접근도 하지 않도록 해야한다. DOM접근은 자바스크립트 객체에서 속성 접근보더 더 느린 순으로 접근이 된다.

Scope $watch Depths

Dirty checking는 3가지 전략으로 수행될 수 있다.
By reference
By collection contents
By Value

이 3가지 전략은 그들이 검출하고 성능적인 특징이 따라 종류가 달라진다.

- Watch by reference (scope.$watch (watchExpression, listener)) 은 다음과 같이 변경을 검출한다. 전체 값이 watch expression 이 새로운 값으로 변경되어 반환된 경우에 검출이 된다. 만약 값이 배열 혹은 객체이면, 내부 변경은 검출되지 않는다. 이것은 가장 효율적인 전략이다.

- Watch collection contents (scope.$watchCollection(watchExpression, listener))은 변경을 다음과 같이 검출한다. 배열 혹은 객체 내부의 변경이 발생하는 경우 : 아이템이 추가, 삭제, 재배열되어지는 경우에 처리된다. 검출은 shallow하게 처리된다. 이것은 중쳑된 컬렉션에서는 많지 않다. 컬렉션 컨텐츠의 watching은 watch by reference보다는 더 비용이 든다. 왜냐하면 컬렉션 컨텐츠가 관리되기 위해서 복사 되기 때문이다. 그라나 이 전략은 복사를 최소한으로 수행한다.

- Watch by value (scope.$watch( watchExpression, listener, true)) 는 임의의 중첩된 데이터 구조의 변경을 검출한다. 이것은 가장 강력한 변경 감지 전략이다. 그러나 가장 비용이 비싸다. 전체 충접 구조의 데이터 구조를 탐색하며, 각 다이제스트에서 수행되어 져야한다. 그리고 전체 복사가 메모리 내에서 이루어져야 한다.

Integration with the browser event loop

아래 나열된 다이어그램과 예제는 어떻게 Angular가 브라우저의 event loop와 상호작용하는지 보여준다.

1. 브라우저 이벤트 루프는 event가 도착하기를 대기한다. event는 사용자 인터렉션, 타이머 이벤트, 혹은 네트워크 이벤트이다. (네트워크 이벤트 = 서버로 부터 결과 전송)

2. 이벤트의 콜백은 수행된다. 이것은 자바스크립트 컨텍스트에 들어간다. 콜백은 DOM구조를 변경할 수 있다.

3. 콜백이 수행되면 브라우저는 JavaScript 컨텍스트를 버리고, 다시 DOM의 변경을 기반으로 뷰를 다시 그린다.

Angular는 일반적인 JavaScript를 변경하며 이는 주어진 이벤트 프로세싱 루프에 의해서 수행된다. 이것은 자바스크립트를 전통적인 방법에서 Angular 수행방식을 분리한다. 오직 Angular 실행 컨텍스트에서만 오퍼레이션이 적용되며, 이것은 Angular data-binding, exeception handling, property watching등으로 부터 수행된다. 또한 $apply를 이용할 수 있으며 이는 JavaScript로 부터 Angular excution으로 들어갈 수 있도록 해준다. 염두해야할 것은 대부분의 장소(controller, service)에서 $apply는 이미 디렉티브에 의해서 호출되어지며 이는 이벤트 핸들링을 수행한다. $apply를 명시적으로 호출하는 것은 custom이벤트 콜백을 구현할때, 혹은 서드파티 라이브러리 콜백과 함께 수행되어야 할때 적용된다.


1. Angular가 scope.$apply(stimulusFn) 호출에 의해서 컨텍스트 수행에 들어갈때 stimulusFn은 Angular에서 수행되고자 하는 작업을 의미한다.

2. Angular는 stimulusFn()을 실행하고 이는 보통 어플리케이션의 상태를 변경한다.

3. Angular는 $digest루프에 들어간다. loop는 $evalAsync큐와 $watch리스트 처리를 수행하도록 만들어 졌다. $digest루프는 모델이 안정화 될때까지 반복한다. 이의미는 $evalAsync 큐가 비고 $watch리스트가 더이상 변경을 감지할 수 없을때 까지이다.

4. $evalAsync큐는 현재 스택 프레임의 외부에서 발생이 필요한 스케쥴 작업에 이용된다. 그러나 브라우저 뷰가 렌더링 하기전에 수행된다. 이것은 보통 setTimeout(0)으로 완료된다. 그러나 setTimeout(0) 접근은 느리고 많은 view 플리킹을 수반한다.

5. $watch리스트는 지난 반복의 변경된 표현식의 셋이다. 만약 변경이 검출되면 $watch함수는 새로운 값으로 DOM의 값을 변경한다.

6. $digest루프가 끝이나면 실행은 Angular와 JavaScript 컨텍스트로 벗어난다. 이것은 다른 변경으로 인해서 DOM이 다시 렌더링 된 다음에 이어진다.

다음은 어떻게 Hello world 예제가 사용자의 입력으로 인해서 데이터 바인딩을 수행하는지 설명한다.
1. 컴파일 단계동안 :
    1. ng-model과 input directive는 keydown을 셋업하고 <input>컨트롤에 리슪ㄴ다.
    2. interpolation은 $watch를 셋업하여 name의 변경을 알림받는다.

2. runtime단계 동안 :
    1. 'X'키가 브라우저에서 눌려졌고 키다운 이벤트가 입력 컨트롤러에서 발생하였다.
    2. input디렉티브는 입력값의 변경을 캡쳐하고, $apply("name = 'X';")를 호출하여 Angular 실행 컨텍스트 내부의 어플케이션 모델을 변경한다.
    3. Angular는 name = 'X'; 를 모델에 적용한다.
    4. $digest루프가 시작된다.
    5. $watch 리스트가 name속성의 변경을 검출하고 interpolation에 알린다. 이것은 DOM업데이트를 수행하게 된다.
    6. Angular는 존재하는 컨텍스가 있으며, interpolation을 검증한다. 이것은 DOM을 수행한다.
    7. 브라우저는 변경된 텍스트로 부라우저를 다시 그린다.










from : https://docs.angularjs.org/guide/scope





Share this

Related Posts

Previous
Next Post »