블로그 이미지
윤영식
Frontend Application Architecter, Full Stacker, KnowHow Dispenser and Bike Rider

Publication

Statistics Graph

Recent Comment

2013.11.28 13:19 AngularJS/Concept

하기 영상을 보면 JSON Object일 때와 String(Primitive type) 일때의 처리가 틀리다 왜그럴까 고민을 해보자 



1. Angular.js scope 상속 방법

  - childe scope는 기본적으로 parent scope를 상속받는다. 

  - 단, directive에서 scope : {...} 설정에서 scope isolate을 정하게 되어있다 

  - 원칙

1) define objects in the parent for your model, then reference a property of that object in the child: parentObj.someProp

    부모 scope에 object를 정의하면 child scope로 상속 된다. 그러나 primitive type(string, number, boolean)은 상속되지 않는다 

2) use $parent.parentScopeProperty (not always possible, but easier than 1. where possible)

    $parent 의 프로퍼티를 사용한다. 항상 가능하진 않으나 1)번 보다는 쉽다

3) define a function on the parent scope, and call it from the child (not always possible)

    부모 scope에 function을 정의하면 child scope로 상속 된다



2. JavaScript Prototypal 상속에 대하여

  - scope chaing에 따른 상속 : childScope는 primitive, object 전부 호출 가능 




3. Angular.js Scope 상속에 대하여 

  - 상속유형 

1) The following create new scopes, and inherit prototypically: ng-repeat, ng-include, ng-switch, ng-view, ng-controller, directive with scope: true, directive with transclude: true.

    ng-repeat, ng-include, ng-switch는 새로운 scope를 만들고 prototypically를 상속을 한다. 

    즉, scope:true, transclude:true인 directive이다 (새로운 scope를 만들지 않는 것은 scope: false 이다)

2) The following creates a new scope which does not inherit prototypically: directive with scope: { ... }. This creates an "isolate" scope instead.

     prototypically사용을 하지 않고 새로운 scope를 만들려면 scope: {...} 를 정의하여 "isolate" scope를 만들어야 한다


  - ng-include의 예

// controller 에서 

$scope.myPrimitive = 50;

$scope.myObject    = {aNumber: 11}; 


// html 에서 

<script type="text/ng-template" id="/tpl1.html">

    <input ng-model="myPrimitive">

</script>

<div ng-include src="'/tpl1.html'"></div>


<script type="text/ng-template" id="/tpl2.html">

    <input ng-model="myObject.aNumber">

</script>

<div ng-include src="'/tpl2.html'"></div>

  - 최초 설정 상태 : ng-include는 새로운 ChildScope1, 2를 생성한다. ParentScope는 ng-include를 감싸는 상위 Controller scope 가정 


  - 파란색 input box에 값을 입력 할 때 : primitive는 child scope에 새로운 property가 생성됨



  - 분홍색 input box에 값을 입력 할 때 : object는 parentScope의 값이 변경됨 


  - 파란색을 분홍색처럼 영향을 미치고 싶다면 하기와 같이 사용하면 parent scope의 primitive 값을 child scope에서 제어가능

     * 펑션도 Object와 동일하게 Child에 상속된다 

<input ng-model="$parent.myPrimitive">

  - ng-switch 도 ng-include와 유사하게 동작한다 



4. ng-repeat 상속의 경우 

  - ng-include나 ng-switch와 약간 틀리다 

// controller 에서 

$scope.myArrayOfPrimitives = [ 11, 22 ];

$scope.myArrayOfObjects    = [{num: 101}, {num: 202}]


// html 에서 

<ul><li ng-repeat="num in myArrayOfPrimitives">

       <input ng-model="num">

    </li>

<ul>


<ul><li ng-repeat="obj in myArrayOfObjects">

       <input ng-model="obj.num">

    </li>

<ul> 

  - 최초 ng-repeat이 새로운 scope를 하나 만들고 iteration 하면서 아이템의 값을 새로운 scope를 또 만들어 새로운 property 에 할당함

    여기서, 새로운 proeperty란 loop 변수 - 위의 예에서 num 또는 obj - 이다. 즉, 하기와 같은 작업이 일어나는 것이다. 

childScope = scope.$new(); // child scope prototypically inherits from parent scope ...     

childScope[valueIdent] = value; // creates a new childScope property

  - 파란색 Primitive 배열 경우 : 새로운 scope가 생기고 loop 변수 num이 Childe scope에 생기고 primitive 값의 복사가 이루어진다. 

    즉, referencing 되지 않아 Parent Scope와 관련성이 없어진다


  - 분홍색 Object 배열 경우 : Child Scope가 생성되고 loop 변수 obj는 parent scope의 배열 요소를 referencing 한다. 

    즉, child에서 변경을 하면 parent scope 값도 변경되는 것이다 


  - ng-controller

    + 일반적인 prototypal 상속을 따른다 

    + controller 끼리 $scope를 통해 정보를 공유하는 것은 좋지 않다. 공유할려면 service를 이용한다 (참조)

  - ng-view

    + ng-include와 동일하다 



5. 사용자 정의 Directive의 Scope 경우

  - directives를 만들 때

    + scope: false 는 기본값이다. directive를 만들면 scope를 새로 생성하지 않는다 

    + scope: true 를 정의하면 일반적인 prototypal 상속을 따른다

    + scope: { ... } 경우는 isolate/isolated scope를 새롭게 생성한다.

  - scope: {...} 경우 상세 고찰 

1) prototypically 상속을 하지 않는다 

2) 재사용 가능한 컴포넌트를 만들 때 사용한다. 즉 컴포넌트가 parent scope의 값을 read/write 못하게 한다

3) 그러나 간혹 parent scope에 접근(access) 하고 싶을 경우 object hash를 사용한다 

    '=' : two way binding (isolate scope <-> parent scope) 

    '@' : one way binding (isolate scope <- parent scope)

    '&' : parent scope expressions 에 바인딩 된다 

4) objec hash는 자신의 directive의 attributes 가 바인딩시에 이용됨을 주의한다 

5) object hash 는 parent scope의 property를 지정 할 수 없다 

    즉, 'parentProp' 가 있을 때 <div my-directives>의 scope: {localProp: '@parentProp'} 지정로 하지 않고, 대신 접근하고 싶다면

    <div my-directives the-Parent-Prop=parentProp>  의 scope: {localProp: '@theParentProp'}로 parent property를 명시해야 한다 (참조 예제)

6) isolate scope 의 attribute 명칭과 parent scope의 property 명칭이 같다면 scope: { attributeName: '=' } 이런식으로 설정함

    틀리면 scope: { keyName1: '@isolateAttributeName', keyName2: '=isolateAttributeName'} 으로 정의함

  - isolate scope 개념도

    + isolate scope 가 생성되면 __proto__ 는 Scope를 레퍼런스 한다 (하기 그림의 오랜지색 박스 'Object')

    + isolate scope의 $parent는 parent scope를 레퍼런스 한다. 그렇다고 prototypically 상속을 parent scope로 부터 하진 않는다 

    + 하기와 같이 정의 하였을 경우 

// html 에서 : attribute 에서 parent scope의 property를 접근한다 

<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">


// directive 에서 : =, @, & 등을 사용하여 parent scope property의 접근 방법을 명시한다 

scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' } 


// directive의 linking function 에서 

scope.someIsolateProp = "I'm isolated" 

    + 위 코드이 개념도 


 - @ 을 사용하면 linking function안에 하기 코드를 사용한다. 

    즉, attrs.$observe('interpolated', function(value) { ... } 의 value에 11을 설정한다  

attrs.$observe('attr_name', function(value) { ... }

  - 따라서 scope.interpolatedProp는 linking function 안에 정의 되지 않으나, 

     scope.twowayBindingProp는 inking function 안의 정의 된다. 이에 대해 해당 링크를 참조하자 (예제)

 


6. Transclude 에 대하여

  - transclude :true 하면 새로운 "transcluded" child scope를 생성한다. 이는 일반적인 prototypically 상속을 한다 

  - transaclude content가 two way binding을 원한다면 $parent를 이용한다

  - isolate scope 객체와 transclude scope 객체는 형제지간으로 $parent는 같은 부모 object를 레퍼런스 한다 (sibling)

  - isolate scope 의 $$nextSibling은 transclude scope 객체를 레퍼런스 한다 



  - 링크 사이트를 참조한다


즉, Isolate Scope를 가지게 되면 일반적인 Prototypal 상속을 따르지 않는다. 그러나 Isolate Scope(== Child Scope)가 간혹 parent scope를 접근하고 싶을 때는 @, =, & 와 같은 object hash를 Directive의 Attribute 앞에 붙임으로써 접근 방법을 지정할 수 있다. 


* 주의) 버전 1.1.* 와 1.2.* 버전사이의 동작이 틀리다. 1.1.* 에서는 정상 작동하지만 1.2.* 에서 Isolate Scope방식이 정상 동작하지 않는다! (이유를 찾아봐야 함)

  예제에서 왼쪽 메뉴의 "External Resource"를 바꾸어 가며 테스트해 보라

  "http://code.angularjs.org/1.1.5/angular.js" <->  "http://code.angularjs.org/1.2.0/angular.js"



<참조>

  - 원문 : Angular.js Scope에 대한 이해 개념도

  - Angular.js Scope 상속 영역 

  - Angular.js Architecture 고려 사항 (필독)

  - Angular.js Isolated Scope에 대한 이해 (필독)

  - Isolated Scope @, =, & 예제

  - Transclude의 two-way binding 실현

  - 본문 다이어그램 소스보기 프로그램

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
2013.04.11 11:50 AngularJS/Concept

Angular는 지시자(Directive)를 통하여 DOM을 확장한다. 지시자에 대해 자세히 알아보자



특징

  - Camel cased name 을 갖는다. 예) ngBind

  - 변환될 때는 Snake case - 으로 변경된다 예) ng-bind

  - 지시자는 element name, attribute, class name, comment 에 올 수 있다

  1. <span my-dir="exp"></span>
  2. <span class="my-dir: exp;"></span>
  3. <my-dir></my-dir>
  4. <!-- directive:my-dir exp -->


  - compiler는 $interpolate 서비스를 이용해서 text와 attribute를 매칭시킨다. 표현이 watch에 등록되어 있고, digest 싸이클의 일부로서 업데이트 되는 것이다 

<a href="img/{{username}}.jpg">Hello {{username}}!</a>


  - HTML에서 이루어지는 3 스텝

    + Step 1: 최초에 Angular의 template은 HTML이므로 표준 브라우져 API를 사용하여 DOM 으로 파싱된다. 다른 템플릿 시스템과 차별점이기도 하다 (참조)

    + Step 2: $compile 메소드를 통해서 DOM을 해석하면서 지시자(directive)를 찾는다. 지시자는 $compile() 펑션이 수행되면서 DOM 구조를 변경하고 응답을 위하여 link() 펑션으로 return 한다

    + Step 3: 각 지시자별로 link() 를 호출하여 지시자별 listener등록과 scope에 대한 watch를 설정한다. 이에따라 scope와 DOM 사이에 Live Binding이 되는 것이다

  1. var $compile = ...; // injected into your code
  2. var scope = ...;
  3.  
  4. var html = '<div ng-bind="exp"></div>';
  5.  
  6. // Step 1: parse HTML into DOM element
  7. var template = angular.element(html);
  8.  
  9. // Step 2: compile the template
  10. var linkFn = $compile(template);
  11.  
  12. // Step 3: link the compiled template with the scope.
  13. linkFn(scope);


  - 왜 compile과 link를 나눈거지? 

    + model이 변경되면 DOM이 변경되어야 한다. 예) ng-repeat의 경우 model 목록이 늘면 DOM도 변경됨

    + {{user}}는 interpolation(값을 채우는) 지시자 이지만 ng-repeat은 ngRepeat 지시자이다. 여기서의 딜레마는 모델 데이터가 늘어나면 li 앨리먼트를 복제하여 compile과정을 또 거쳐야 하는데 그렇게 하면 성능이 느려진다. 이에 대한 해결책은 과정을 두개로 나누는 것이다. 그래서 ngRepeat 지시자를 해석하고 linking function 을 얻는다. ngRepeat 은 표현이 변경되면 li 엘리먼트를 복제하여 배열에 추가하고 새로운 scope 생성하여 복제한 li 엘러먼트에 할당하고 복제한 li 의 linking function 을 호출한다

  1. Hello {{user}}, you have these actions:
  2. <ul>
  3. <li ng-repeat="action in user.actions">
  4. {{action.description}}
  5. </li>
  6. </ul>


  - 정리

    + compile function : 지시자에 따라 template DOM 엘리먼트를 변환하여 특별한 DOM 엘리먼트를 만들어 준다 

    + link function : 지시자가 복제된 DOM 엘리먼트에 listener 등록하도록 해준다 



지시자 만들기

  - 전체 내역 

  1. var myModule = angular.module(...);
  2.  
  3. myModule.directive('directiveName', function factory(injectables) {
  4. var directiveDefinitionObject = {
  5. priority: 0,
  6. template: '<div></div>',
  7. templateUrl: 'directive.html',
  8. replace: false,
  9. transclude: false,
  10. restrict: 'A',
  11. scope: false,
  12. compile: function compile(tElement, tAttrs, transclude) {
  13. return {
  14. pre: function preLink(scope, iElement, iAttrs, controller) { ... },
  15. post: function postLink(scope, iElement, iAttrs, controller) { ... }
  16. }
  17. },
  18. link: function postLink(scope, iElement, iAttrs) { ... }
  19. };
  20. return directiveDefinitionObject;
  21. });


  - 기본 내역 : 없은 것은 기본값을 사용한다 

  1. var myModule = angular.module(...);
  2.  
  3. myModule.directive('directiveName', function factory(injectables) {
  4. var directiveDefinitionObject = {
  5. compile: function compile(tElement, tAttrs) {
  6. return function postLink(scope, iElement, iAttrs) { ... }
  7. }
  8. };
  9. return directiveDefinitionObject;
  10. });


  - 간단 내역 : 지시자가 템플릿 변환이 없고 자신의 인스턴스에 대한 것만 관련 있을 때 사용함 

  1. var myModule = angular.module(...);
  2.  
  3. myModule.directive('directiveName', function factory(injectables) {
  4. return function postLink(scope, iElement, iAttrs) { ... }
  5. });


  - Factory method : 지시자를 생성하는 역할이고, compiler가 지시자를 찾을 때 최초 호출하여 초기화 한다. $injector.invoke 를 사용한다 


  - 지시자 정의하기 

    + 지시자 정의는 compiler 에게 이렇게 이렇게 해달라는 설명을 제공하는 과정이다 

    + name : 현재 scope의 명칭 (optional)

    + priority : compile 펑션의 호출 우선 순위를 지정한다

    + terminal : true ?

    + scope : true 이면 새로운 scope 생성, {} 새로운 격리된 scope 생성으로 부모 scope 상속이 없다. 새로운 컴포넌트 만들 때 유용한다

      {} 로 만들때 안의 내용들 

        > @ : local scope property

        > = : bi-directional binding between local property and the parent scope property 

        > & : parent scope 에서 execute expression  방법을 제공 

///////////////

// @ 사용하기

/*
local scope property html
<div ng-app="drinkApp">
<div ng-controller="AppCtrl">
<div drink flavor="strawberry"></div>
</div>
</div>*/
 
var app = angular.module("drinkApp", []);
 
app.controller("AppCtrl", function($scope) {
$scope.ctrlFlavor = "blackberry";
})
 
app.directive("drink", function() {
return {
scope: {
flavor: "@"
},
template: '<div>{{flavor}}</div>'
/*
scope: {flavor: "@"} 표현은 link: function() {...} 과 완전히 동일하다
link: function(scope, element, attrs) {
scope.flavor = attrs.flavor;
}*/
}
})


///////////////

// = 사용하기 (소스)

/*
flavor="{{ctrlFlavor}}" 를 사용하지 않음에 주의한다
<div ng-app="drinkApp">
<div ng-controller="AppCtrl">
Ctrl
<input type="text" ng-model="ctrlFlavor">
Dir
<div drink flavor="ctrlFlavor"></div>
</div>
</div>
*/
 
var app = angular.module("drinkApp", []);
 
app.controller("AppCtrl", function($scope) {
// parent property
$scope.ctrlFlavor = "blackberry";
})
 
app.directive("drink", function() {
return {
scope: {
flavor: "="
},
// local property
template: '<input type="text" ng-model="flavor"> '
}
})


///////////////

// & 사용하기 (소스)

/*
message를 controller parent scope로 전달 이는 scope가 isolated되어 있어도 상위로 값을 전달 할 수 있게 한다
<div ng-app="phoneApp">
<div ng-controller="AppCtrl">
<div phone dial="callHome(message)"></div>
<div phone dial="callHome(message)"></div>
</div>
</div>
*/
 
var app = angular.module("phoneApp", []);
 
app.controller("AppCtrl", function($scope) {
$scope.callHome = function(message) {
alert(message);
}
})
 
app.directive("phone", function() {
return {
scope: {
dial: "&"
},
// {message:value} 객체를 통하여 contrller scope의 callHome에 전달
template: '<input type="text" ng-model="value">' +
'<div class="button" ng-click="dial({message:value})">Call Home</div> '
}
})


/////////////////////////

// @, =, & 종합 사용하기 (소스)

/*
@, &, = integration scope
<div ng-app="phoneApp">
<div ng-controller="AppCtrl">
<phone number="1111111" network="network" make-call="leaveVoicemail(number, message)"></phone>
<phone number="2222222" network="network" make-call="leaveVoicemail(number, message)"></phone>
<phone number="3333333" network="network" make-call="leaveVoicemail(number, message)"></phone>
 
</div>
</div>
*/
 
var app = angular.module("phoneApp", []);
 
app.controller("AppCtrl", function($scope) {
$scope.leaveVoicemail = function(number, message) {
alert("Number: " + number + " said: " + message);
}
})
 
app.directive("phone", function() {
return {
restrict: "E",
scope: {
number: "@", // local scope property
network: "=", // bi-directional binding between local property and parent property
makeCall: "&" // send expressions to parent scope
},
template: '<div class="panel">Number: {{number}} Network:<select ng-model="network" 
ng-options="network for network in networks">' +
'</select></div><input type="text" ng-model="value">' +
'<div class="button" ng-click="makeCall({number: number, message:value})">
Call Home</div> ',
link: function(scope) {
scope.networks = ["KT", "SKT", "LGU+"];
scope.network = scope.networks[0];
}
}
})

  

    + controller : pre-linking 전에 호출되어서 지시자들끼리 공유를 할려고 할때 사용

    + require : 다른 controller에 현재 지시자의 linking function을 전달 할때 사용용

    + restrict : 지시자 선언 종류 

  • E - Element name: <my-directive></my-directive>
  • A - Attribute: <div my-directive="exp"> </div>
  • C - Class: <div class="my-directive: exp;"></div>
  • M - Comment: <!-- directive: my-directive exp -->


    + template : HTML file 

    + templateUrl : 로딩할 URL 지정

    + replace : true 이면 현재 엘리먼트를 append가 아닌 replace 한다 

    + transclude : ngTransclude 엘러먼트로 컴파일 및 지시자로 쓰임 

    + compile : compile function 정의

    + link : link function 정의 


  - Compile function

    + template DOM을 변환하는 역할이다 

    + 사용자 정의할 때만 사용하면 되고 기존 지시자에는 사용할 일이 없겠다

function compile(tElement, tAttrs, transclude) { ... }

    + tElement : template element 

    + tAttrs : template attribute

    + transclude : transclude linking function: function(scope, cloneLinkingFn)

    + return 값을 두가지로 줄 수 있다

     > function 형태로 리턴 : linking function 으로 등록하는 것과 동일함 

     > object 형태로 리턴 : pre-linking, post-linking을 제어하고 싶을 때 사용한다 


  - Linking function

    + DOM 업데이트를 위하여 DOM listener를 등록하는 역할이다 

    + template 이 복제된 후에 수행되고 지시자의 대부분 로직이 놓인다 

function link(scope, iElement, iAttrs, controller) { ... }

    + scope : watch 등록할 때 지시자에 의해서 사용 됨 

    + iElement : instance element. 

    + iAttrs : instance attribute

    + controller : controller instance

    + pre-linking function : child element가 link 되기전에 호출

       post-linking function : child element link 된 후에 호출 


  - Attribute Object 

    + compile() 과 link() 에 파라미터로로 전달되는 객체

    + 값의 변화를 $observe 할 수도 있고 $set하거나 .XXX 로 읽을 수도 있다

  1. function linkingFn(scope, elm, attrs, ctrl) {
  2. // get the attribute value
  3. console.log(attrs.ngModel);
  4.  
  5. // change the attribute
  6. attrs.$set('ngModel', 'new value');
  7.  
  8. // observe changes to interpolated attribute
  9. attrs.$observe('ngModel', function(value) {
  10. console.log('ngModel has changed value to ' + value);
  11. });
  12. }



Transclusion 과 Scope 이해하기 

  - 재사용 컴포넌트를 만들고자 하는 다음의 경우를 생각해 보자 

    + show 버튼 클릭하면 다이얼로그 박스가 열린다

    + 다이얼로그 박스는 제목이 있고, 

  1. <div>
  2. <button ng-click="show=true">show</button>
  3. <dialog title="Hello {{username}}."
  4. visible="show"
  5. on-cancel="show = false"
  6. on-ok="show = false; doSomething()">
  7. Body goes here: {{username}} is {{title}}.
  8. </dialog>
  9. </div>

     + dialog 위젯이 변환되면 아마 하기와 같이 나올것이다 

      > username이 들어간 title을 넣어 줘야 한다 

      > onOk, onCancel 버튼이 동작해야 한다 

      > scope 에서 local 변수에 대한 맵핑작업이 필요하다 

  1. <div ng-show="visible">
  2. <h3>{{title}}</h3>
  3. <div class="body" ng-transclude></div>
  4. <div class="footer">
  5. <button ng-click="onOk()">Save changes</button>
  6. <button ng-click="onCancel()">Close</button>
  7. </div>
  8. </div> 


    + scope 정의 

     > transclude DOM 은 다이얼로그 위젯의 격리된 scope의 child 가 되어서 어느 것과도 binding되지 않고 영향을 미지치치 않고 title에 값을 맵핑할 수 있다. 즉, transclude scope는 original scope의 자식이 되기 때문이다 (아직 이해 실패^^;) 

  1. transclude: true,
  2. scope: {
  3. title: '@', // the title uses the data-binding from the parent scope
  4. onOk: '&', // create a delegate onOk function
  5. onCancel: '&', // create a delegate onCancel function
  6. visible: '=' // set up visible to accept data-binding
  7. },
  8. restrict: 'E',
  9. replace: true


  - Transclude 옵션을 설정하는 간단한 예제 (소스)

/*
Simple transclude sample dom
<div ng-app="phoneApp">
<div ng-controller="AppCtrl">
<panel>
<div class="button">Click me!</div>
</panel>
</div>
</div>
*/
 
var app = angular.module("phoneApp", []);
 
app.controller("AppCtrl", function($scope) {
 
})
 
// transclude: true와 <div ng-transclude를 설정하지 않으면 DOM의 <div class="button"이 안보이고 
// template의 내용만 보이게 된다. template 내용안에 DOM 의 button class가 같이 보이게 하기 위한 방법

> 버튼이 안보임


> transclude 설정으로 버튼이 보임


app.directive("panel", function() {
return {
restrict: "E",
transclude: true,
template: '<div class="panel" ng-transclude>This is a panel component</div>'
}
})


전반적으로 처음 접하는 개념이어서 정리가 서툴다. 다시 반복하여 리팩토링 예정임... ^^



미스코가 직접 이야기하는 Directive에 대해서 들어보자 

  - 유튜브 

    + Directive의 compile & link 개념의 C의 컴파일과 링크에서 영감을 받았다 

    + compile은 scope를 핸들링하지 않는다. template자체를 변경할 수 있다. 즉 compile은 template에 대한 해석만을 담당한다.

    + link는 element instance에 대해서 조작을 한다. 예로 element.addClass()와 같은 호출. 

    


  - 디렉티브 만들기 : 간단 총정리하기 

    


<참조>

  - 원문 : Developer Guide - Directives

  - AngularJS Directive로 컴포넌트 만들기 (필독)

  - 동영상강좌 : http://egghead.io

    + isolated @, =, & 소스 : https://gist.github.com/ysyun/5385927

    + 테스트를 위한 index.html 파일 내역 : CSS로 foundation을 사용한다 

 - Directives 이해 높이기-소스포함 (필독)

 - Directives Link, Apply, Digest 동작 흐름

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
prev 1 next