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

Publication

Category

Recent Post

2016. 1. 14. 08:23 Angular/Concept

이번에는 Angular2 Templating에 대한 Design Guide를 살펴본다. Angular1에서 Angular2로 오면서 살아남은 명칭중 하나는 Directive(지시자)이다. 지시자는 Angular 프레임워크에서 컴포넌트의 지위를 얻을 수 있는 방법을 제공했으나, Scope에 대한 격리 옵션과 모델들의 @/&/= 옵션, ControllerAs 등 문법적으로 알고 가야할 사항이 많아서 처음 접근하는 개발자에게 상당한 Learning Curve가 존재한다.




친숙함과 간결함 사이에서 (Familiarity vs Simplicity)


친숙함은 달콤하다. 지금 잘 알고 있는 것을 가지고 현재를 편하게 즐길 수 있도록 해준다. 하지만 변화에 둔하고 러닝커브가 높을 경우 신규로 알아야 할 사람들에게 많은 시간을 투자하게 만든다. 친숙함이 정말 친숙한 것인지 한번 더 돌아보고 주위 사람들이 왜(Why) 어려워 하고 어떻게(How)하면 쉽고 빠르게 익히고 널리 사용할 수 있도록 무엇(What)으로 만들 수 있을지 기술 리더는 고민해 보아야 한다. 


간결함은 하는 역할이 명확하여 쉽게 이해한다. 배움의 시간을 단축시켜주고 즐거움을 줄 수 있다. 그러나 복잡함을 간결하게 표현하기 위해서는 함축적이다. 따라서 그 내면에 있는 내용을 이해하고 쓰면 더 많은 생각들을 읽을 수 있다. 따라서 간결함을 왜 어떻게 무엇으로 만들었는지 알아 보는 것이 중요하겠다. 그 간결함을 쉽게 설명할 수 있다면... Angular2 Templating은 간결함의 시작중 하나라 본다. 


* 주의) 글에서 Angular 또는 Angular2는 Angular version 2를 지칭하고, AngularJS 또는 Angular1는 Angular version 1를 가르킨다. 


 

Templating 개념 둘러보기


Angular는 DOM element와 행위에 대한 것을 연관(연결) 한다. 여기서 행위(Behavior)를 Directive라고 부른다. 행위에 대한 부분이기 때문에 Directive는 CSS Selector와 맵핑되는 DOM element를 위해 인스터스화된 자바스크립이다. DOM element는 애플리케이션과 상호작용하기 위해 properties와 events를 사용하는데, Directive도 DOM element의 properties와 events의 동일 인터페이스를 사용한다. 


Angular 애플리케이션이 사용자 인터페이스는 "Component Directive"를 사용해 구성한다. Component Directive는 애플리케이션의 Data와 Logic을 저장하고, 또한 사용자 인터페이스를 정의하는 html template도 가진다. 컴포넌트 안에서 element와 상호작용하는 컴포넌트는 Decorator Directive이다. 보통 ng-show="false"와 같이 element의 attribute로 동작하고 엘러먼트에 여러개가 놓일 수 있다. 


Angular는 양방향 데이터 바인딩을 사용한다. 데이터 바인딩은 template안에 특별한 html attribute를 이용해 설정한다. 이때 바인딩은 expression이라고 부르는 것을 이용해 정의한다. expression은 함수(Function)를 호출하거나 데이터에 접근하는 것으로 모든 expression은 execution context안에서 실행된다. 이런 작용은 Dependency Injection Container 안에서 아래와 같이 수행된다.





Directive Type


지시자의 종류 목록


Provides execution context for expressions

Provides a hole in the DOM to insert template instances

Isolates the DOM/expressions/css for reuse

Decorator Directive

no

no

no

Template Directive

can create child execution context, don't have to.

yes

no

Component Directive

isolated execution context, always.

no

yes


Directive에 대한 일반 제약조건은 Directive는 DOM 구조를 제거(remove)/변경(change) 해서는 안된다. Template Directive와 Component Directvie는 별도의 Execution Context를 가질 수 있다. Angular에서 template directive가 execution context를 갖는 것에 유념하자.



Decorator Directive


새로운 element를 만들고 해당 element에서 뭐든 할 수 있다. 그러나 만들지 않은 것은 다른 element는 attribute 변경만 가능하다.



Template Directive


원래 있던 element/template 영역의 특정 부분에 template 인스턴스를 넣을 수 있고, template directive는 자식(child) execution context를 생성한다. 예로 ng-repeat는 row 마다 execution context를 만들지만 ng-if는 부모(parent) execution context를 재사용한다. Template Directive 종류로는 ng-if, ng-repeat, ng-view, ng-switch, ng-include 가 있다. 일반적인 문법을 보자 

<template ng-repeat>

  <div> ... </div>

</template>


template element 자체는 제거되고 template의 일부가 되지 않는다. 즉, template directive는 <template> element만을 허용한다. 중첩으로 아래와 같이 사용할 수도 있다. 

<template ng-repeat>

  <template ng-if>

    <div>...</div>

  </template>

</template>

    

template directives를 사용하면 element에 아래와 같이 template로 변경된다. 

<ul>

  <li ng-repeat>

    <span ng-if> ... </span>

  </li>

</ul>

은 아래 내용과 완전 동일하다

<ul>

  <template ng-repeat>

    <li>

      <template ng-if>

        <span> ... </span>

      </template>

    </li>

  </template>

</ul>


위이 template 태그는 Angular1의 directive의 compile/link 과정과 동일하다. 즉, Angular2에서는 compile/link과정을 template directive로 대체하는 것이다. 


    // Angular1의 이런 구문이 사라지는 것이다. 

    return {  

           ....

            templateUrl: 'plugins/taskers/wafer-traffic/wafer-traffic.html',

            replace: true,

            link: link,

            controller: ctrl,  

            ....

        };

    function link() {}

    function ctrl() {}



Component Directive 


javascript 로직, html template 과 옵션으로 css style을 컴포넌트에 가지고 있다. 이를 위해서 template안의 expressions을 격리(isolate)한다. - execution context의 격리: component directive 인스턴스가 template안의 expresssions를 위한 새로운 execution context가 되는 것이다. 즉, template안의 expressions은 component directive 인스턴스의 함수(function) 또는 properties만 접근 가능하다는 것이다. 이것은 Angular1의 directive 정의시 scope: {...} 격리하는 방법과 controllerAs를 사용해서 this로 접근하는 것등을 개발자가 정의할 필요없이 Component directive로 통일한 것으로 보인다. 프로젝트를 진행하다보면 거의 대부분 scope: { model: '=' } 과 controllerAs를 default로 놓고 사용하기 때문에 프레임워크 단에서 simple화 한 것은 환영할만 하다. 


  // Angular1의 이런 구문이 사라지는 것이다. 

  return {

            restrict: 'EA',

            scope: {

              model: '='

            },

            controller: trafficCtrl,

            controllerAs: 'traffic',

            bindToController: true

        };


- DOM 과 CSS의 격리: Component directives는 다른 directive에서 컨텐츠 변경을 못하도록 Shadow DOM을 사용한다. 따라서 Shadow DOM안에서의 element events는 외부 element로 전파되지 않는다. (buble out of component directive) 

   + Properties와 events: Template은 component의 properties들과 데이터 바인딩 되고, 이벤트는 상위 컴포넌트로 전파할 수 있다. 

   + CSS properties: 템플릿은 Shadow DOM을 기본으로 사용하는 컴포넌트에 한해 스타일을 변경할 수 있다. 

   + Child fragment: 템플릿은 Shadow DOM의 <content> 태그를 사용하는 템플릿을 넣는 자식 elements를 제공한다. 자식 elements의 expression은 밖의 템플릿 execution context와 계속 연결된다. 


expression을 위한 Execution Context는 클래스 인스턴스이고 이를 통해 expression에 대한 type checks등 다양한 일을 수행할 수 있다. 



Directives를 인스턴스화 하는 순서 


자식 elements전에 부모 element의 directives가 먼저 인스턴스화 하고, 만약 template directive가 존재하면 이것을 인스턴스화 하고, 다음으로 element의 decorator directive를 인스턴스화 한다. 순서는 directive 생성자의 dependency injection 순서에 의해 결정된다. 그리고 나서 최종 component directive가 인스턴스화 한다. 즉, 다음과 같다. 


>> Parent Element 

     template directive --> decorator directives --> component directive

         >> Child Elements

                template directive --> decorator directives --> component directive




templates에 데이터 바인딩 설정 방법


elements 또는 directives의 events 와 바인딩 하기


형식: @on-[event name]="[expression]" 

        예) <button on-click="doSomething()">

의미: click 이벤트가 발생하면 doSomething을 호출한다. 

DOM events를 기반으로 하는 thrid party 이벤트 시스템을 바인딩 할 수 있고, Directives에 새로운 이벤트를 정의해도 listener를 달 필요가 없다.



elements 의 properties 와 바인딩 하기


형식: @bind-[property name]="expresion" 

         예) <input bind-value="user.name">

의미: expression인 user.name에 대한 값 변경을 감시하다가 element property값을 갱신 한다. 


만일 element의 property가 바뀌었는데 expression은 writable하지 않거나, expression의 값이 바뀌었는데 property가 writable하지 않으면 에러가 발생한다. 양방향 데이터 바인딩은 directive와 element property 사이에 일어나기 때문이다. element의 attribute로 하지 않고 property로 바인딩 하는 이유는 다음과 같다. 


  - 거의 대부분의 attribute는 대응하는 property를 가진다. 만일 대응하는 property가 없으면 attribute가 대신한다. 

  - element properties는 항시 현재 값을 가진다. 그러나 attributes는 때때로 초기값을 지정해 주어야 한다. 예) <input>의 value attribute.

  - native attributes는 브라우저가 알아서 자동으로 대응하는 property 값을 바꿔준다. 

    예) <img>의 src property는 src attribute 바뀌면 자동을 변경된다)



elements의 properties 넣기 


형식: @[property name="a {{ [expression] }} b"

        예) <input title="some Text {{someValue}}">

의미: <input bind-title=" 'a ' + someValue + ' b' ">  


{{}} 를 사용하면 항상 string으로 변환하고, 단방향 연결이 된다. Angular1 directive의 @ 과 같다. 



text 넣기


형식: {{ [expression] }}

        예) hello {{user}}

의미: hello <span bind-text-content=" ' ' + user "> 


text node 내역과 데이터 바인딩도 항시 string으로 변환되고, 단방향이다. 



Guide-2 이어보기



<참조>


  - Templating 디자인 가이드 

  - Template Syntax 개발자 가이드 



posted by 윤영식
2015. 10. 22. 10:41 AngularJS



본 서적은 자바스크립트와 앵귤러 프레임워크를 프로젝트에 도입해 보려는 개발자 또는 프로토타입을 해보고 싶은 개발자 또는 사용해 본 경험이 있는 개발자를 대상으로 한다. 앵귤러에 대해 처음 공부하는 분에게는 다소 어려울 수 있으므로 기초서적을 권장드린다. 책 제목처럼 3장이후 부터 실전 프로젝트 실습을 위한 상세 소스를 GitHub에서 제공한다. 1~2장은 개념 정리이므로 읽고 나중에 참조하는 장으로 사용한다. 참고로 2장은 실습이 없이 개념을 설명하는 장이기에 소스를 제공하지 않고 있다. (소스의 Git Branch는 장이 끝나는 시점에 언급하고 있다. 먼저 전체 소스를 보고 싶다면 GitHub 내용을 참조하자)








출판의 여정 


8개월의 원고작성, 3개월의 편집기간을 거쳐 드디어 책이 출간되었다. 책을 통해 개발자들과 공유하자는 의기투합은 2013년 겨울로 거슬러 올라간다. 이강훈님을 통해 알게된 이규원님, 김충섭님, 김대권님, 박유진님, 구교준님과 함께 MEAN 스택을 기획했었다. 그 당시 미국에 있었던 지라 구글 행아웃을 통해 주말마다 화상미팅을 하며 깃헙과 페이스북을 통해 서로의 의견과 결과물을 공유했다. 2014년 봄 귀국후 함께 오프라인에서 헤커톤도 하면서 책의 샘플을 만들며 진행했지만 각자의 일정으로 인해 MEAN 스택 집필을 흐지부지 되어갔다. 2014년 7월쯤 결단을 내리고 이렇게 진행하면 끝나지 않겠다 싶어서 다음 기회를 기약하였고, 홀로 책을 쓰기로 결심했다. 


2014년 8월 이강훈님 회사인 스터디지피에서(https://bagle.io)의 서비스를 개발하면서 미국 플젝에서 얻은 AngularJS 경험과 개념정리가 덜 된 부분을 하나하나 집어가며 집필을 시작했다. 처음에는 한 페이지를 나가는데 하루의 시간이 걸리더니 조금씩 속도가 붙으면서 한달만에 100p 가량을 썼다. 하지만 다시 가을 미국 프로젝트로 2달간 집필 중단 후 한국에 돌아와 마음을 다잡고 12년전 내 책을 출판해 보자는 꿈을 다시한번 상기했다. 위키북스의 박찬규 대표님도 은근히 압력을 넣어주시며 언제쯤 나올까요 물어볼때마다 곧 나올겁니다 대답하곤 하루 1~2시간씩 짬을 내어 집필을 계속했다. 2015년 3월 집필을 완료하고 출판사에 전달했지만 피드백으로 날아온 것은 맞춤법과 문장 난해함에 대한 엄청난 지적질의 빨간줄이었다. 


한달간 주말마다 빨간 선생님과 싸움하며 "그래 글쓰는 것도 배우는 거다"라고 생각하고 나의 국어실력을 한탄하며 글을 풀어서 이야기식으로 고쳐나갔다. 사실 블로그를 쓰다보니 블릿(점, 점으로 요약한 내용)으로 내용을 요약해 표현하다 보니 책도 그렇게 썼드랬다. 블릿 내용을 다 풀어쓰는 것만도 한달. 다시 피드백받아 또 고치고 피드백받아 또 고치고, "아 무한루프의 피드백". 점점 스트레스와 화가 살짝 났다. 인생 목표 10가지중 1가지가 1년에 책한권씩 내자였던 것이 여지없이 깨지는 순간이었다. 이렇게 하다가는 2년에 한권내는 것도 힘들겠다 싶었다. 하지만 일에는 끝이 있는 법. 5월초 거의 마무리 피드백을 보내고, 책 추천사를 받고 인덱스를 보내고, 몇가지 피드백을 마무리하며 3개월의 수정작업은 6월 5일 마무리되었고, 예약 판매가 모든 온라인 서점에서 시작되었다. 그렇게 나온 것이 "실전 프로젝트로 배우는 AngularJS" 이다. 


본 서적은 AngularJS가 하도 유행이라 하여 한번쯤은 기초서적을 구매하거나 빌려서 본적이 있고, 온라인 튜토리얼을 통해 프로토타입핑을 해보았지만 당췌 AngularJS로 서비스를 어떻게 만들어야 할지 모르는 개발자를 위한 책이다. "나도 내가 원하는 서비스 뚝딱 만들어 보고 싶어 근데 어떻게 시작해야는 거야?"라고 생각하는 모든 개발자를 위한 책이다. AngularJS는 생태계를 가지고 있고 인식의 전환을 해야하는 새로운 용어들이 존재한다. 


처음 1장에서는 간단한 ToDo 서비스를 만들면서 주변의 생태계 도구와 개념을 이해하고 2장에서는 AngularJS의 장점들과 그중에 지시자(Directive)에 대해 좀 더 심도있게 설명을 했다. 3장, 4장에서 서비스를 바로 만들 줄 알았다면 오산이다. 이제 서비스를 만들 준비운동단계이다. 먼저 요건정의를 하고 이에 맞는 코딩 컨벤션 부터 AngularJS를 이용한 SPA 개발시 준비할 것들을 프론트앤드 아키텍처로 접근해 보았다. 5장에서 이제 좀 개발들어가려 했더니 그래도 요즘 많이 사용하는 NodeJS, MongoDB는 알고가자는 취지에서 개념과 실습코드를 넣었다. 1장~5장이 준비운동과 스타트 단계였다면 6장, 7장은 엄청난 스피도로 골인점을 향해가는 단계이다. 올초 2개월간 TossLab의 JANDI(http://www.jandi.com)에서 AngularJS에 대한 컨설팅 경험을 적용한 장이다. 





목차


▣ 01장: 단일 페이지 애플리케이션 개발 준비
1-1. 개발 도구 설치 
   - 깃 설치 
   - 노드 설치 
   - 요맨 설치 
   - 서브라임 텍스트 편집기 설치 

1-2. 단일 페이지 애플리케이션 생성 
   - yo generator 선택과 설치 
   - Yo를 이용한 ToDo 애플리케이션 생성 
1-3. 애플리케이션 컴포넌트 생성 
   - 앵귤러를 위한 index.html 설정 이해하기 
   - yo를 이용한 앵귤러 컨트롤러 추가 
   - bower를 이용한 앵귤러 지시자 추가 
1-4. 애플리케이션 테스트 및 빌드 
   - grunt를 이용한 테스트 
   - grunt를 이용한 배포 
정리 

▣ 02장: AngularJS 프레임워크 이해
2-1. MV* 프레임워크 
2-2. 양방향 데이터 바인딩 
   - 스코프 내부와 상속 관계  
   - MyToDo 애플리케이션에서 양방향 데이터 바인딩 
   - 스코프 생명 주기(Life Cycle) 
   - 그 외 $scope 객체 메서드 
2-3. 의존성 주입(DI, Dependency Injection) 
2-4. 클라이언트 템플릿 
2-5. 지시자(Directive) 
   - 지시자가 DOM에 적용되는 순서 
   - 지시자 정의 
   - 지시자의 스코프 객체의 범위 종류 
   - Template, TemplateUrl, TemplateCache, replace와 ng-template 사용 
   - compile, link의 $watch 등록을 이용한 양방향 데이터 바인딩 
   - controller, require와 link 네 번째 파라미터와의 관계 
   - transclude, ng-transclude 사용 
2-6. 테스트 프레임워크(단위, E2E) 
   - 카르마 기반 단위 테스트 
   - 프로트랙터 기반 E2E 테스트 
정리 

▣ 03장: 싱글 페이지 애플리케이션 기획및 생성
3-1. 애플리케이션 기획
   - 메인 페이지 
   - 그룹 정보 페이지 
   - 그룹 활동 페이지 
   - 설문 생성 페이지 
3-2. 애플리케이션 제너레이터 설계 
   - 애플리케이션의 폴더 구조 전략 
   - 애플리케이션 제너레이터 선정 
   - 앵귤러 코드 스타일 전략 
   - 스타일 가이드에 따른 제너레이터 템플릿 수정 방법 
   - IE8 지원을 위한 index.html 설정 
3-3. SPA 생성 
   - 애플리케이션의 모듈 구성 
   - 라우팅 설정 방식 
3-4. 단위 업무를 위한 앵귤러 컴포넌트 조합 
   - $resource를 통한 REST 모델 사용 
   - promise와 $q Async 호출에 대한 이해 
정리

▣ 04장: 애플리케이션을 위한 공통 프레임워크 개발
4-1. 공통 프레임워크 모듈 개발 
   - 다국어 처리 
   - 메시지 처리 
   - 팝업 메시지창 지시자 
   - HTTP 에러 처리 
   - 사용자 정의 Bower 컴포넌트 등록 
   - 로컬 저장소 서비스 
   - 유틸리티 지시자 
4-2. 로그인 화면 개발 
   - 트위터 부트스트랩 기반의 화면 디자인 및 폰트 사용 
   - 폼 유효성(Form Validation) 검사 
   - 인증을 위한 토큰과 쿠키 
4-3. OAuth를 이용한 인증 처리 
   - 백엔드에서 Passport 모듈을 이용한 인증 처리 
   - 페이스북 인증 처리 
   - 크롬 브라우저 개발자 도구를 이용한 클라이언트 디버깅 
   - 노드 인스팩터를 이용한 서버 디버깅 
정리

▣ 05장: 메인 페이지 개발
5-1. 백엔드 API 개발 
   - REST API 별 서버 모듈 조합 
   - 노드 모듈의 exports 이해 
   - 몽고디비와 몽구스 이해 
   - 서버 모델 개발 
   - 그룹 REST API 개발 
   - 포스트맨을 이용한 REST API 검증 
   - 백엔드 단위 테스트 수행 
5-2. 메인 화면 개발 
   - 공통 컴포넌트 재구성 
   - 메인 화면 레이아웃 개발 
   - 그룹 생성 
5-3. 그룹 목록 및 정보 표현 
정리 

▣ 06장: 그룹 페이지 개발
6-1. 그룹 정보 페이지 
   - 그룹 상세 정보 조회 
   - 그룹 프로필 이미지 변경 
   - 그룹 가입, 탈퇴 
6-2. 그룹 활동 페이지 
   - 그룹 활동 화면 레이아웃 개발 
   - 그룹 멤버 목록 표현 
6-3. 설문 카드 생성 
   - 설문 카드 생성 
   - 카드 지시자 개발 
6-4. 설문 종류별 카드 표현 
6-5. 설문 응답 및 결과 표현 
정리

▣ 07장: 실시간 반응 개발
7-1. Socket.IO 기반 실시간 연동 
   - 노드 기반 백엔드 Socket.IO 
   - AngularJS 기반 프런트엔드 Socket.IO 
   - 상단 알림 메뉴 추가 
7-2. 카드 목록 UX 개선 
   - 카드에 동영상 추가 
   - 무한 스크롤 적용 
   - 애니메이션 효과 적용 
7-3. AngularJS 성능 옵션 
   - 일회 바인딩 
   - ngModelOptions 지시자 
   - 디버깅 정보 비활성화 
   - $applyAsync 적용 

정리




구매


예스24 온라인 서점


알라딘 온라인 서점


인터파크 온라인 서점


교보문고 온라인 서점


G마켓 온라인 서점


현재는 예약 판매로 10% 싸게 구매 할 수 있다. 6월 17~19일간 배송을 시작한다. 당초 높은 가격이 책정되었지만 위키북스 대표님에게 가격이 높아 개발자에게 부담이 될 것 같다하였더니 파격적인(?) 가격에 판매를 시작해 주셨다. 지금까지 독자로 책을 사보면서 철자하나 틀리면 왠지 기분이 나쁘고 지적을 해주고 싶었는데 이제부터는 너그러이 용서해 줄것 같다. 정말 많은 공력과 시간이 투여되는 시간이었다. 하지만 새로운 경험을 하게 되었고 이렇게 해서 책 한권 한권이 출판된다는 생각을 하니 한권의 책을 사도 감사히 볼 마음가짐이 생긴다.  


이번달 중으로 출판사의 양해를 얻어 1장과 2장은 블로그에 공개하려 한다. 2장은 AngularJS v1.* 이 계속 사용되는 한 유용한 레퍼런스가 되리라 생각한다. 이렇게 생애 첫 책 출판을 자축하며... 내년에는 "Data Visualization using D3.js" 이고, 여기에 ReactJS 또는 AngularJS v2.* 와 Meteor를 접목한 경험을 출판할 계획이다. 글쓰기도 가끔은 중독이다. 자전거 처럼 잘 타지는 못하지만 타고 있는 동안은 자유다. 나를 표현하고 느낄 수 있는 시간이랄까~~~




소스 


책의 소스는 모두 깃헙에 있습니다. 책 챕터마도 브랜치에 대한 정보가 담겨 있습니다. 


책 소스를 위한 깃헙 그룹 : https://github.com/AngularJS-SPA-Development



posted by 윤영식
2014. 8. 4. 00:11 AngularJS/Concept

Highchart에 대한 Directive를 만들며서 controller, compile, link 등을 써보고 있는데, 성능 최적화 및 실시간 데이터 업데이트 방법등에 대해서 고민하면서 좋은 글이 있어서 머리도 정리할 겸 따라쟁이 해본다. 



1. Directive 사용 영역

  - HTML안에서 element, attribute, class name, comment 4영역

<angular></angular>

<div angular></div>

<div class="angular"></div>

<!-- directive: angular -->



2. 이름 규칙

  - HTML안에서 하이픈(hyphens) 으로 단어 사이를 구분하여 사용

  - JavaScript안에서 단어사이는 대문자로 구분하여 사용

<div the-quick-silver-rider-youngsik></div>


App.directive('theQuickSilverRiderYoungsik', function() { ... });



3. 테스트전 SaaS

  - http://plnkr.co/  코드 테스트 Preview 서비스를 이용한다

  - GitHub 아이디로 로그인 한후 -> [Launch the Editor] 한다

  - 자신의 테스트 코드 및 타인의 코드를 서로 공유할 수 있다

  - 해당 서비스 자체가 AngularJS 기반으로 만들어 졌다 (Node.js + AngularJS 소스 공개)

  - 그림 오른쪽의 angular.js 라이브러리 버전을 선택하면 index.html에 자동 삽입되고 코딩을 시작할 수 있다



4. 간단한 테스트 수행

  - 사용자 정의 태그를 만든다

  - 이름 : angular

// index.html

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 3) add the created custom 'angular' directive -->
    <angular></angular>
  </body>

</html>


// script.js

var App = angular.module('App', []);
 
App.directive('angular', function() {
  return {
    restrict: 'ECMA',
    link: function(scope, element, attrs) {
      var img = document.createElement('img');
      img.src = 'http://goo.gl/ceZGf';
 
 
      // directives as comment
      if (element[0].nodeType === 8) {
        element.replaceWith(img);
      } else {
        element[0].appendChild(img);           
      }
    }
  };
});


  - plunker preview


  - <angular> 태그에 <img> 태그가 추가되었다



5. Directive Definition

  - AngularJS는 기동될 때(bootstrapped), 사용자정의 및 빌트인된 directives를 찾고 $compile() 메소드를 이용하여 컴파일을 한다

  - compile 메소드 역할 : 모든 directives 요소를 찾아서 우선순위 sort하고 link function을 만든다. 또한 scope와 element사이의 2-way data binding을 위하여 $watch를 셋팅하거나 event listener를 생성된 (ng-app, ng-controller, ng-include, etc)같은 scope에 link한다  

  - Directive 정의

var myModule = angular.module(...);
myModule.directive('directiveName', function (injectables) {
  return {
    restrict: 'A',
    template: '<div></div>',
    templateUrl: 'directive.html',
    replace: false,
    priority: 0,
    transclude: false,
    scope: false,
    terminal: false,
    require: false,
    controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
    compile: function compile(tElement, tAttrs, transclude) {
      return {
        pre: function preLink(scope, iElement, iAttrs, controller) { ... },
        post: function postLink(scope, iElement, iAttrs, controller) { ... }
      }
    },
    link: function postLink(scope, iElement, iAttrs) { ... }
  };
});



5-1. Compile function

  - compile function이 link function을 만든다(produce)

  - 2개 파라미터

    + tElement : directive가 선언되어진 template element (jQuery object이다. 따라서 $로 다시 wrap하지 마라)

    + tAttrs : tElement와 관련된 모든 attributes 목록

  - ng-repeat같은 것을 사용할 경우 성능상의 이슈로 compile function이 사용된다. DOM과 scope객체를 연결지으려는데 데이터를 가져올 때마다 바인드과정을 거치지 않고 최초 한번 바인드되고 link functioin을 통해서 데이터 변경시에 scope-DOM간의 2-way 바인딩을 하여 변경을 반영한다

// in index.html

<div compile-check></div>


// in script.js

App.directive('compileCheck', function() {
   return {
     restrict: 'A',
     compile: function(tElement, tAttrs) {
       tElement.append('Added during compilation phase!');
     }
   };
 });


  - index.html에 <div compile-check></div>를 추가하고 script.js에 코드를 추가하면 하단에 'Added during compilation phase!'가 첨부된다



5-2. Link function

  - DOM과 scope 객체를 2-way data binding 하는 역할이다. compile function에서는 scope 객체에 접근할 수 없다

  - $watch 메소드를 통하여 사용자정의 listener를 생성할 수 있다. (register등록은 angular가 자동으로 해준다)

  - 3개 파라미터

    + element : directive와 바인드된 요소

    + scope : directive에 의해 사용되어지는 scope 객체

    + attrs : 요소와 관련된 모든 attribute목록

 


5-3. Restrict

  - E : elements

  - A : attributes

  - C : class name (CSS)

  - M : comments

  - HTML5 호환 표기가 가능한다 : 빌트인 ng-bind 사용

<span ng:bind="name"></span>
<span ng_bind="name"></span>
<span ng-bind="name"></span>
<span data-ng-bind="name"></span>
<span x-ng-bind="name"></span>



5-4. Template

  - html에 넣은 요소와 교체할 내역

  - html안의 태그 반복되는 부분을 별도의 모듈화로 중복을 방지 할 수 있다 

  - https://api.github.com/repos/angular/angular.js 호출하면 angular.js 관련 정보가 JSON 형태로 나옴

// index.html

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 3) add controller & whoiam directive -->
    <div ng-controller="DemoCtrl">
      <div whoiam>This text will be overwritten</div>
    </div>
  </body>
 
</html>


// script.js

var App = angular.module('App', []);

App.run(function($rootScope) {
  $rootScope.header = 'I am bound to rootScope';
});
 
App.controller('DemoCtrl', function($scope) {
  $scope.footer = 'I am bound to DemoCtrl';
});
 
App.directive('whoiam', function($http) {
  return {
    restrict: 'A',
    template: '{{header}}<div class="thumbnail" style="width: 80px;"><div><img ng-src="{{data.owner.avatar_url}}"/></div><div class="caption"><p>{{data.name}}</p></div></div>{{footer}}',
    link: function(scope, element, attrs) {
      $http.get('https://api.github.com/repos/angular/angular.js').success(function(data) {
        scope.data = data;
      });
    }
  };
});


  - $scope는 $rootScope를 상속받기 때문에 template에서 {{header}}, {{footer}}를 호출 할 수 있다



5-5. TemplateUrl

  - 템플릿 마크업 및 별도의 html 파일로 관리한다

  - template 파일의 로딩을 빠르게 할려면 메모리에 적제하여 사용한다. $templateCache 

  - <script> 태그를 이용한다

<script type='text/ng-template' id='whoiam.html'></script>


  - inde.html 내역 : template script 태그를 head 태그안에 넣는다. id로 whoiam.html을 사용한다

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
   
    <!-- 3) add the template script tag -->
    <script type='text/ng-template' id='whoiam.html'>
      <div class="thumbnail" style="width: 260px;">
          <div><img ng-src="{{data.owner.avatar_url}}" style="width:100%;"/></div>
          <div class="caption">
            <p><b>Name: </b>{{data.name}}</p>
            <p><b>Homepage: </b>{{data.homepage}}</p>
            <p><b>Watchers: </b>{{data.watchers}}</p>
            <p><b>Forks: </b>{{data.network_count}}</p>
          </div>
        </div>
     </script>
  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 4) add controller & whoiam directive -->
    <div ng-controller="DemoCtrl">
      <div whoiam>This text will be overwritten</div>
    </div>
  </body>
 
</html>


  - script.js 내역 

var App = angular.module('App', []);

App.controller('DemoCtrl', function($scope) { });
 
App.directive('whoiam', function($http) {
  return {
    restrict: 'A',
    templateUrl: 'whoiam.html',
    link: function(scope, element, attrs) {
      $http.get('https://api.github.com/repos/angular/angular.js').success(function(data) {
        scope.data = data;
      });
    }
  };
});


  - 호출한 angular.js 정보가 출력된다



5-6. Replace

  - directive를 설정한 태그를 템플릿 태그로 교체하고자 할때 따라서 template 또는 templateUrl과 함께 사용한다

  - replace: true

  - index.html : elment, attribute, class name, comment 4가지 설정

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>

  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 3) add angular directive -->
    <div angular></div>
    <div class="angular"></div>
    <angular></angular>
    <!-- directive: angular -->
  </body>
 
</html>


  - script.js 내역

var App = angular.module('App', []);

App.directive('angular', function() {
  return {
    restrict: 'ECMA',
    template: '<img src="http://goo.gl/ceZGf" />',
    replace: true
  };
});


  - 결과 html

// attribute

<img src="http://goo.gl/ceZGf" angular=""></img>


// class name

<img class="angular" src="http://goo.gl/ceZGf"></img>


// element

<img src="http://goo.gl/ceZGf"></img>


// comment

<img src="http://goo.gl/ceZGf" angular=""></img>


  - img 태그가 4개 생성되어서 이미지가 4개 보인다



5-7. Priority

  - 우선순위에 따라서 compile/link의 순위를 정한다. 값이 클 수록 우선순위가 높다

  - 이것은 미리 컴파일된 directive의 결과값을 체크하여 수행할 필요가 있을 때 사용한다

  - zero이면 priority 설정을 하지 않고 default priority를 사용한다

  - bootstrap의 'btn-primary' 클래스를 적용하려고 할때 index.html

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link href="style.css" rel="stylesheet" />
    <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
    <script src="script.js"></script>

  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 3) add angular directive -->
    <div style='padding:100px;'>
      <div primary btn>The Hitchhiker Guide to the Directive Demo by Dowon</div>
    </div>
  </body>
 
</html>


  - script.js 내역 : 우선순위로 btn이 먼저 생성되었으므로 primary에 대한 적용이 가능해 진다

var App = angular.module('App', []);

App.directive('btn', function($timeout) {
  return {
    restrict: 'A',
    priority: 1,
    link: function(scope, element, attrs) {
      element.addClass('btn');
    }
  };
});
 
App.directive('primary', function($http) {
  return {
    restrict: 'A',
    priority: 0,
    link: function(scope, element, attrs) {
      if (element.hasClass('btn')) {
        element.addClass('btn-primary');
      }
    }
  };
});


  - bootstrap의 btn-primary CSS가 적용된 버튼이 보인다



5-8. Terminal

  - 가장 최근에 수행된 directive에 terminal: true를 설정하면 그 이후 directive는 수행되지 않는다

  - index.html 내역

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link href="style.css" rel="stylesheet" />
    <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
    <script src="script.js"></script>

  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 3) add angular directive -->
    <div first second></div>
    <ul>
        <li ng-repeat="item in ['one', 'two', 'three']" no-entry>{{item}} </li>
    </ul>
  </body>
 
</html>


  - script.js 내역 : first -> second -> noEntry 가 수행되지만 second에 terminal: true 설정으로 매noEntry는 수행되지 않는다

  - ng-repate, ng-switch는 priority 높음에도 나중에 수행됨. 버그?

var App = angular.module('App', []);

App.directive('first', function() {
  return {
    restrict: 'A',
    priority: 3,
    link: function(scope, element, attrs) {
      element.addClass('btn btn-success').append('First: Executed, ');
    }      
  };
});
 
App.directive('second', function() {
  return {
    restrict: 'A',
    priority: 2,
    terminal: true,
    link: function(scope, element, attrs) {
      element.addClass('btn btn-success').append('Second: Executed ');
    }      
  };
});
 
App.directive('noEntry', function() {
  return {
    restrict: 'A',
    priority: 1000,
    link: function(scope, element, attrs) {
      element.append('No Entry: Executed ');
    }      
  };
});


  - first, second의 priority 값을 서로 바꾸어 보라 : 버튼의 text 순서가 바뀔 것이다. 또는 noEntry의 priority값을 1000이하로 하면 directive가 실행된다



5-9. Controller

  - Directive를 제어를 담당한다.

  - $scope, this 를 사용하여 properties/methods를 바인할 수 있다

  - this 키워드로 바인딩된 데이터는 require옵션을 사용하여 controller를 injecting함으로써 다른 directives에서 엑세스할 수 있게 한다.

  - 5-10의 require에서 예제 진행함

App.directive('powerSwitch', function() {
      return {
        restrict: 'A',
        controller: function($scope, $element, $attrs) {
          $scope.state = 'on';
 
          $scope.toggle = function() {
            $scope.$apply(function() {
              $scope.state = ($scope.state === 'on' ? 'off' : 'on');
            });
          };
 
          this.getState = function() {
            return $scope.state;
          };
        },
     };
});



5-10. Require

  - controller를 다른 directive에 전달해서 compile/linking function안에 연관시킬 수 있다

  - 같은 element 또는 부모와 바인드 되어야 한다

  - prefixed 부호가 붙는다

    + ? : 지정한 directive가 없더라도 에러가 발생시키지 않는다

    + ^ : 부모 element위의 directive를 찾는다. 같은 element위치는 유효하지 않다


  - index.html 내역 

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link href="style.css" rel="stylesheet" />
    <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
    <script src="script.js"></script>

  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 3) add angular directive : parent -> child depth  -->
    <div class="btn btn-success" power-switch>
      {{'Switched ' + state | uppercase}}
    
      <div lightbulb class='bulb' ng-class="bulb"></div>
    </div>
  </body>
 
</html>


  - script.js 내역

var App = angular.module('App', []);

App.directive('powerSwitch', function() {
  return {
    restrict: 'A',
    controller: function($scope, $element, $attrs) {
      $scope.state = 'on';
 
      $scope.toggle = function() {
        $scope.$apply(function() {
          $scope.state = ($scope.state === 'on' ? 'off' : 'on');
        });
      };
 
      this.getState = function() {
        return $scope.state;
      };
    },
    link: function(scope, element, attrs) {
      element.bind('click', function() {
        scope.toggle();
      });
 
      scope.$watch('state', function(newVal, oldVal) {
        if (newVal === 'off') {
          element.addClass('disabled');
        } else {
          element.removeClass('disabled');
        }
      });
    }
  };
});
 
App.directive('lightbulb', function() {
  return {
    restrict: 'A',
    require: '^powerSwitch',
    link: function(scope, element, attrs, controller) {
      scope.$watch(function() {
        scope.bulb = controller.getState();
      });
    }
  };
});


  - 변경내역 : toggle off일 경우

  <div class="btn btn-success ng-binding disabled" power-switch="">
     
      SWITCHED OFF
     
    <div class="bulb off" ng-class="bulb" lightbulb=""></div></div>


  - 결과 화면 : uppercase 필터 사용



5-11. Scope

  - scope가 hierarchy하게 생성/유지되면 element와 부모 element사이의 scope를 설정한다.

  - 그러나 자신은 부모 Scope하고만 바인드되어 data에 접근 할 수 있다. 최상위 scope는 $rootScope 객체이다

  - scope: false

    + 새로운 scope 객체를 만들지 않고 부모가 가진 같은 scope 객체를 공유한다

  - scope: true

    + 새로운 scope 객체를 만들고 부모 scope 객체를 상속받는다 


  - index.html 내역

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>

  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 3) add angular directive : parent -> child depth  -->
    <div class='well'>
      Bound to $rootScope: <input type="text" ng-model="parent">
      <div child></div>   
    </div>
 
    <div class='form well' ng-controller="OneCtrl">
      ng-controller="OneCtrl"<br/>
     
      <input type="text" ng-model="children"><br/>
      I am {{children}} and my grandfather is {{parent}}
     
      <div child></div>
    </div>
  </body>
 
</html>


  - script.js 내역 : child 디렉티브의 link에서 conole.log(scope)를 찍어본다

var App = angular.module('App', []);

App.run(function($rootScope) {
    $rootScope.parent = 'ROOT';
});

App.directive('child', function() {
    return {
        restrict: 'A',
        // scope: false,
        scope: true,
        template: 'I am a directive and my father is {{parent}} as well as {{children || "NIL"}}',
        link: function(scope, element, attrs) {
            console.log(scope);
        }
    };
});

App.controller('OneCtrl', function($scope) {
    $scope.children = 'The One';
});


  - 결과화면 : child의 scope: true, false로 변경해 가면서 console.log의 내역을 비교해 본다

 

  - scope: true

// 첫번째 child의 $parent객체를 보면 ROOT이다


// 두번째 child의 $parent 객체를 보면 controller의 scope.children='The One' 값이 보임. 즉, controller scope가 부모이다


// Chrom에서 보면 scope 구조도가 보인다



  - scope: false

// 첫번째 child의 $parent객체를 보면 ROOT이다


// 두번째 child의 $parent 객체를 보면 ROOT 이다


// 002 는 ROOT scope 이고 그 밑으로 controller scope만 존재하고 child는 이것을 공유한다




  - scope: 'isolate'

    + 부모 scope를 상속받지 않는다. 그러나 scope.$parent로 부모 scope를 억세스 할 수 있다 

    + iscope.$parent을 사용하지 않고 isolated scope와 부모 scope간의 데이터 공유방법은 어떻게 하는가?

       isolated scope는 당신이 부모 scope로 부터 properties를 끌어내어 local scope와 바인드하는 object/hash를 갖는다  

       1) @ : 부모 scope property local scope 값을 바인딩한다. 원하는 값 전달시 {{}} 를 사용한다

       2) = : 부모 scope (전달전 측정되어질) property 를 직접 바인딩 한다  

       3) & : 속해있는 scope context안에서 수행될 expression 또는 method 바인딩 한다 

 

  - prefix parent property @, children property =, shout() method & 를 준 예제

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>

  </head>

  <body>
    <h1>Hello Angular</h1>
    <!-- 3) add angular directive : parent -> child depth  -->
    <div class='well'>
      Bound to $rootScope: <input type="text" ng-model="parent">
      <div child parent='{{parent}}' shout="shout()"></div>   
    </div>
 
    <div class='form well' ng-controller="OneCtrl">
      ng-controller="OneCtrl"<br/>
      <input type="text" ng-model="children"><br/>
      I am {{children}} and my grandfather is {{parent}}
      <div child parent="{{parent}}" children="children" shout="shout()"></div>   
    </div>

  </body>
 
</html>


  - script.js 내역

r App = angular.module('App', []);

App.run(function($rootScope) {
    $rootScope.parent = 'ROOT';
   
    $rootScope.shout = function() {
        alert("I am bound to $rootScope");
    };
});

App.directive('child', function() {
    return {
        restrict: 'A',
        scope: {
            parent: '@',
            children: '=',
            shout: '&'
        },
        template: '<div class="btn btn-link" ng-click="shout()">I am a directive and my father is {{parent || "NIL"}} as well as {{children || "NIL"}}</div>'
    };
});

App.controller('OneCtrl', function($scope) {
    $scope.children = 'The One';
   
    $scope.shout = function() {
        alert('I am inside ng-controller');
    };
});


  - 결과화면

 

  * scope: isolate 에 대한 이해는 egghead.io 사이트를 참조한다



5-12. Transcude

  - ngTransclude를 통하여 DOM에 transcluded DOM을 insert 할 수 있다

  - transclude: true
    transcluded DOM을 template에서 ngTransclude directive에 삽입한다

// index.html

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">

  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
    <!-- 3) template -->
    <script type='text/ng-template' id='whoiam.html'>
      <div class="thumbnail" style="width: 260px;">
        <div><img ng-src="{{data.owner.avatar_url}}" style="width:100%;"/></div>
        <div class="caption">
          <p><b>Name: </b>{{data.name}}</p>
          <p><b>Homepage: </b>{{data.homepage}}</p>
          <p><b>Watchers: </b>{{data.watchers}}</p>
          <p><b>Forks: </b>{{data.network_count}}</p>
          <marquee ng-transclude></marquee>
        </div>
      </div>
    </script>
</head>

<body style='padding:30px;'>
  <div whoiam>I got transcluded</div>
</body>
</html>



// script.js

var App = angular.module('App', []);

App.directive('whoiam', function($http) {
   return {
     restrict: 'A',
     transclude: true,
     templateUrl: 'whoiam.html',
     link: function(scope, element, attrs) {
       $http.get('https://api.github.com/repos/angular/angular.js').success(function(data) {
         scope.data = data;
       });
     }
   };
 });



// DOM 내역

<div whoiam="">

  <div class="thumbnail" style="width: 260px;"><div>

   <img style="width:100%;" ng-src="https://secure.gravatar.com/avatar/f0d91e5cf8ad1ce7972cc486baa20c42?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-org-420.png" src="https://secure.gravatar.com/avatar/f0d91e5cf8ad1ce7972cc486baa20c42?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-org-420.png"></img>

</div>


<div class="caption">

  <p class="ng-binding"> … </p>

  <p class="ng-binding"> … </p>

  <p class="ng-binding"> … </p>

  <p class="ng-binding"> … </p>

  <marquee ng-transclude="">

    <span class="ng-scope">
      I got transcluded
    </span>

  </marquee>

 </div>


// 결과 화면 : I got transcluded 메세지가 오른쪽에서 왼쪽으로 움직인다


  - transclude: 'element'

    + transclude link function을 compile function안에 정의한다

// index.html

<!DOCTYPE html>
<!-- 1) add the ng-app attribute -->
<html ng-app="App">
  <head>
    <!-- 2) add the angular library -->
    <script data-require="angular.js@1.1.5"
      data-semver="1.1.5"
      src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
    <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
</head>

<body style='padding:30px;'>
  <div transclude-element>I got transcluded <child></child></div>
</body>
</html>


// script.js

var App = angular.module('App', []);
App.directive('transcludeElement', function() {
    return {
        restrict: 'A',
        transclude: 'element',
        compile: function(tElement, tAttrs, transcludeFn) {

           // return 펑션이 link 펑션이 된다. 해당 펑션안에서 scope 객체 데이터 바인딩을 제어한다
            return function (scope, el, tAttrs) {
                transcludeFn(scope, function cloneConnectFn(cElement) {
                    tElement.after('<h2>I was added during compilation </h2>').after(cElement);
                });
            };
        }
    };
});

App.directive('child', function(){
    return {
        restrict: 'E',
        link: function($scope, element, attrs) {
            element.html(' with my child');
        }
    };
});


// DOM 내역

<body style="padding:30px;">
      <!-- transcludeElement:  -->
    <div class="ng-scope" transclude-element="">
      I got transcluded
      <child>
         with my child
      </child>

    </div>

    <h2>
      I was added during compilation
    </h2>

</body>


// 결과 화면



<참조>

  - 원문 : The Hitchhiker’s Guide to the Directive

  - Scoep Isolate 참조 : egghead.io 사이트

  - Yeoman 사용하기

posted by 윤영식
2014. 6. 17. 14:33 AngularJS/Concept

이제 모든 서비스는 모바일 먼저 기획을 하고 웹 서비스로 넘어가야 한다고 생각한다. 즉, Contents -> UX -> Design -> Develop -> Launch로 넘어가면서 그 기본은 모바일이 먼저이다. 기존에 Bootstrap을 사용했을 경우 RWD (Response Web Design)이 적용되어 해상도에 따른 사용자 경험을 데스크탑과 모바일에 준하게 줄 수 있었지만 네이티브 앱과 유사한 UX 경험을 제공하기에는 부족하다. HTML5 기반으로 개발을 한다면 RWD 외에 Mobile First 전략을 가지고 있는 하이브리드 웹앱 Framework을 살펴 볼 필요가 있다. 살펴볼 두가지의-ionic, onsenui - 하이브리드 앱(모바일 웹앱) 프레임워크는 PhoneGap + Angular.js 와 결합되는 형태를 취하고 있다. mobile angular ui는 PhoneGap과 연결시키는 별도의 작업을 수반한다. 




                       




Ionic 하이브리드 웹앱 프레임워크

  - Ionic 홈페이지

  - PhoneGap에 최적화 되어 프로젝트를 생성해 준다.

  - https://github.com/driftyco/ionic-starter-sidemenu 처럼 ionic-starter-xx의 sidemenu, tabs, blank 3가지 타입을 제고하고 있다

// 설치 

$ sudo npm install -g cordova 

/usr/local/bin/cordova -> /usr/local/lib/node_modules/cordova/bin/cordova

cordova@3.5.0-0.2.4 /usr/local/lib/node_modules/cordova


$ sudo npm install -g ionic

/usr/local/bin/ionic -> /usr/local/lib/node_modules/ionic/bin/ionic

ionic@1.0.14 /usr/local/lib/node_modules/ionic


// 프로젝트 생성

$ ionic start myAppSideMenu sidemenu

Running start task...

Creating Ionic app in folder /Users/prototyping/myAppSideMenu based on sidemenu project

DOWNLOADING: https://github.com/driftyco/ionic-app-base/archive/master.zip

DOWNLOADING: https://github.com/driftyco/ionic-starter-sidemenu/archive/master.zip

Initializing cordova project.

Fetching plugin "org.apache.cordova.device" via plugin registry

Fetching plugin "org.apache.cordova.console" via plugin registry

Fetching plugin "https://github.com/driftyco/ionic-plugins-keyboard" via git clone


$ cd myAppSlideMenu


  - ionic 수행

$ ionic platform add android

Running platform task...

Adding platform android

.. 생략 ..

Project successfully created.

Installing "com.ionic.keyboard" for android

Installing "org.apache.cordova.console" for android

Installing "org.apache.cordova.device" for android


$ ionic build android

Running build task...

Building platform android

Running command: /Users/prototyping/myAppSideMenu/platforms/android/cordova/build

Buildfile: /Users/prototyping/myAppSideMenu/platforms/android/build.xml

... 중략 ...

-post-build:

     [move] Moving 1 file to /Usersprototyping/myAppSideMenu/platforms/android/ant-build

     [move] Moving 1 file to /Users/prototyping/myAppSideMenu/platforms/android/CordovaLib/ant-build

debug:

BUILD SUCCESSFUL

Total time: 48 seconds


// Android의 AVD를 먼저 실행해야 한다 

$ ionic emulate android

Running emulate task...

Emulating app on platform android

Running command: /Users/nulpulum/development/studygps_company/prototyping/myAppSideMenu/platforms/android/cordova/run --emulator

... 중략 ...

BUILD SUCCESSFUL

Total time: 9 seconds

Installing app on emulator...

Using apk: /Users/prototyping/myAppSideMenu/platforms/android/ant-build/HelloCordova-debug-unaligned.apk

Launching application...

LAUNCH SUCCESS


-- AVD 통해 본 모습

  

* 안드로이드 AVD 관리하기 (참조)


 - ionic 분석

 - ionic 명령을 통하여 ionic 프레임워크와의 통합 빌드작업을 자동 수행해 준다

// 완벽하게 angular.js 기반으로 운영된다 (angular.js v1.2.12 버전 번들링)

// www/lib/ionic/js/ionic.bundle.js 


// index.html 

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">

    <title></title>


    <link href="lib/ionic/css/ionic.css" rel="stylesheet">

    <link href="css/style.css" rel="stylesheet">


    <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above

    <link href="css/ionic.app.css" rel="stylesheet">

    -->


    <!-- ionic/angularjs js -->

    <script src="lib/ionic/js/ionic.bundle.js"></script>


    <!-- cordova script (this will be a 404 during development) -->

    <script src="cordova.js"></script>


    <!-- your app's js -->

    <script src="js/app.js"></script>

    <script src="js/controllers.js"></script>

  </head>


  <body ng-app="starter">

    <ion-nav-view></ion-nav-view>

  </body>

</html>

  - ionic components



Onsen UI 프레임워크 

  - PhoneGap을 설치와 Onsen ui 설치는 별개로 진행된다. 따라서 ionic과 같이 PhoneGap에 대한 기본 지식이 필요하다  

  - Onsen ui는 angular.js directive를 제공하는데 주력한다

  - PhoneGap 환경 설정 

// PhoneGap

$ npm install -g cordova

$ cordova create MyAppOnsenui && cd MyAppOnsenui

Creating a new cordova project with name "HelloCordova" and id "io.cordova.hellocordova" at location "/Users/prototyping/MyAppOnsenui"

Downloading cordova library for www...

Download complete


$ cordova platform add android

Creating android project...

Creating Cordova project for the Android platform:

Path: platforms/android

Package: io.cordova.hellocordova

Name: HelloCordova

Android target: android-19

Copying template files...

Running: android update project --subprojects --path "platforms/android" --target android-19 --library "CordovaLib"

Resolved location of library project to: /Users/prototyping/MyAppOnsenui/platforms/android/CordovaLib

Updated and renamed default.properties to project.properties

..중략..

Project successfully created.


-- build와 emulate 전에 하기의 Onsen ui 설치와 설정을 먼저 한 후에 수행한다 


$ cordova build android

.. 중략 ..

BUILD SUCCESSFUL

Total time: 32 seconds


-- android의 AVD를 먼저 실행해야 한다 

$ cordova emulate android


  - Onsenui components : 가장 기본적인 것들을 제공함 

  - Phonegap과 Angular.js 의 결합을 개발자가 직접해야함 

  - 활발한 Community feedback이 없는 상태이다 (참조)

  - Onsenui설치

// Onsen ui

$ cd MyAppOnsenui


$ bower init  명령으로 bower.json 파일을 만들거나 기존의 generator-angular-fullstack의 것을 사용한다

bower install onsenui --save

{

  "name": "mobiconsoft-mobile-app",

  "version": "0.1.0",

  "dependencies": {

    "angular": ">=1.2.*",

    "json3": "~3.3.1",

    "es5-shim": "~3.2.0",

    "angular-resource": ">=1.2.*",

    "angular-cookies": ">=1.2.*",

    "angular-sanitize": ">=1.2.*",

    "angular-ui-router": "~0.2.10",

    "restangular": "~1.4.0",

    "jquery": "1.11.1",

    "modernizr": "~2.8.1",

    "console-shim": "*",

    "jStorage": "~0.4.8",

    "angular-gettext": "~0.2.9",

    "ladda-studygps": "~0.9.4",

    "angular-ladda-studygps": "~0.1.7",

    "onsenui": "~1.0.4"

  },

  "devDependencies": {

    "angular-mocks": ">=1.2.*",

    "angular-scenario": ">=1.2.*"

  },

  "testPath": "test/client/spec"

}


-- http://onsenui.io/OnsenUI/project_templates/sliding_menu_navigator.zip 에서 샘플 파일을 다운로드 받아서 app 폴더에 있는 것을 www 폴더로 복사한다. 그리고 index.html안에 cordova.js를 넣는다. (개발시에는 404 NOT Found 발생함)


cordova build android

cordova emulate android

-- AVD로 띄워본 결과

 


  - Sliding Menu에 있어서는 Ionic 보다는 Onsenui가 좀 더 세련되어 보임 



Mobile Angular UI

  - Slide Menu 와 기본적인 모바일 화면 구성 컴포넌트를 angular.js 기반으로 Directive화 해놓았다. 

  - 친숙한 Bootstrap 기반의 UI를 제공한다. 그러나 Twitter bootstrap의 RWD기반은 아니다



ngCordova

    

  - cordova에서 제공하는 API를 angular 에서 사용할 수 있도록 Angular Service로 만들어 놓았음



좀 더 잘 정비된 것을 쓰고 싶다면 Ionic을 기초 기술부터 체크하면 갈 것이라면 Onsenui를 추천한다. 또한 좀 더 세련되 UI는 Onsenui에 점 수를 더 주고 싶다. 그리고 서로의 아키텍쳐에 대한 부분과 커스터마이징 여부는 좀 더 들여다 보아야 할 것으로 보인다. 각각의 상황과 특성에 따라 선택하여 사용하면 될 것으로 보인다. 



<참조>

  - Ionic 시작하기 

  - onsenui 시작하기

  - onsenui sliding menu zip 

sliding_menu_navigator.zip


  - mobile angular ui

  - iSO 8 UI Framework 7

  - ngCordova

  - 용어 정의 

    

posted by 윤영식
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 윤영식
2013. 11. 28. 09:32 HTML5, CSS3/CSS

Bootstrap은 CSS Framework으로 프리젠테이션을 담당하고 jQuery UI 와 Angular.js는 자체 컨트롤도 가지고 있지만 DOM 핸들링을 한다. 만일 서비스를 만드는데 Bootstrap + jQuery UI 조합일 때 또는 Bootstrap + Angular.js 조합일 때 그리고 Bootstrap + jQuery UI + Angular.js 조합 일때 프리젠테이션을 통합하는 방법은 어떤 것들이 있을까? 각 조합에 대해 어떻게 사용할지 알아보자.



1. Twitter Bootstrap 만 사용할 경우 

  - 부트스트랩만 놓고 보면 CSS Framework으로써 기본적인 Fluid Layout 형태와 기본 HTML 컨트롤의 모양을 정의하고 있다

    또한 JavaScript 기반 다양한 component들도 포함한다 

  - 부트스트랩의 CSS 부분을 향상시키고 extra component를 추가한 부분 변경모델은 Bootflat, Bootplus, FlatStrap, LeapStrap

  - 또는 다양한 완성체 템플릿을 startBootstrap같은 곳에서 만들어 제공도 한다 

    



2. Twitter Bootstrap + jQuery UI  함께 사용할 경우 (이제 없어졌다)

  - 문제는 여기서 부터 시작한다 

  - Bootstrap의 CSS 스타일이 존재하고 jQuery UI 도 자체 theme을 CSS로 정의하고 있다

  - Boottrap의 CSS를 jQuery UI에 접목하여 사용할 경우는 jQuery UI Widgets 에 대해서 Bootstrap theme을 입힌 것을 사용한다

    http://addyosmani.github.io/jquery-ui-bootstrap/

    



3. Twitter Bootstrap + Angular.js 함께 사용할 경우

  - Angular에 JavaScript component를 포팅하기 위하여 Directive로 감싸야하는 문제가 있다

  - jQuery UI와 Bootstrap theme을 결합하였다면 Angular.js 와 Bootstrap theme 을 결합하고 있다

  - 하지만 이것은 Bootstrap v2.3.* 버전의 component를 Angular.js Directive로 구현하는 것이다

    http://angular-ui.github.io/bootstrap/

    



4. Twitter Bootstrap + jQuery UI + Angular.js 함께 사용할 경우 

  - 해당 형태는 3번의 연장선으로 보면 된다 

  - Twitter Bootstrap의 javascript component를 Angular.js Directive화 하였듯, jQuery UI component들도 Directive화 한것이다 

    http://angular-ui.github.io/

     

   - 소스 : https://github.com/angular-ui/


기존의 JavaScript Component를 처리하려면 Directive화 과정을 거쳐야 하는데 이런 번거로운 과정을 해소하고자 Bootstrap의 JavaScript Component를 Directive로 만들어 놓은 Angular UI Bootstrap이 나왔다. jQuery UI를 지원하기 위하여 Angular UI 에 상당부분 즐겨 사용하는 JavaScript Component를 포함시켰으나 이 또한 jQuery UI의 모든 것을 커버하고 있지는 못하다. 


이래저래 문제되는 것은 Angular.js를 기반으로 개발할 때 UI를 담당하는 JavaScript Component를 Angular.js의 Directive화를 잘 해야 한다는 것이다. 



<참조>

  - Bootstrap ToolBox : 다양한 UI 컴포넌트들 

posted by 윤영식
2013. 8. 21. 11:41 AngularJS/Concept

AngularJS에 대해 어떻게 생각하시나요? 그냥 webpage를 만드는 Front-end 단의 JavaScript Framework 정도로 생각하시나요? AngularJS는 클라이언트의 브라우져에서 SPA(Single Page Application)를 구현하기 위한 프레임워크입니다. Webpage를 만드는 것이 아니라 SPA의 A처럼 애플리케이션을 만드는 프레임워크라는 것이지죠. Adobe의 Flex 프레임워크와 대비하여 생각해도 되지 않을까 합니다. AngularJS로 개발한다면 새로운 관점에서 바라보아야 합니다. Angular Way의 길에 들어오세요



1. 페이지를 디자인 하지 말고 DOM(Document Object Model) 조작을 통해 페이지를 변경하라 

  - "나는 몇개의 DOM 조각을 가지고 있다 이것을 가지고 뭔가 동작하는 것을 만들고 싶다"라는 생각에서 AngularJS는 출발한다 

  - 완성하고 싶은 것이 있으면 애플리케이션 아키텍쳐 디자인을 한 다음 마지막에 화면 디자인을 한다 



2. AngularJS 와 함께 jQuery 에 대해서 말하지 마라 

  - AngularJS를 사용하면 jQuery 를 바탕으로 개발하는 것은 배제하길 바란다. Angular Way를 가라~~

  - jQuery 플러그인을 AngularJS에서 사용하기 위해 $apply를 남발하여 코드를 꼬아 놓지 말자. jQuery 플러그인 대부분은 약간의 코드조각으로 AngularJS에서 재작성되어 질 수 있다 (Directive로 만들어서 사용하기)



3. 아키텍쳐라는 단어를 항상 생각하라

  - SPA (Sinlge Page Application)는 application 이다. webpage가 아니다. 서버사이드 개발과 똑같이 생각하라

  - 따라서 모듈화 하고 확장성 있고 테스트 가능하게 개발해야 한다 


3-1. 화면은 공식적으로 표현되는 것 

  - jQuery 경우를 보자 

<ul class="main-menu">
    <li class="active">
        <a href="#/home">Home</a>
    </li>
    <li>
        <a href="#/menu1">Menu 1</a>
        <ul>
            <li><a href="#/sm1">Submenu 1</a></li>
            <li><a href="#/sm2">Submenu 2</a></li>
            <li><a href="#/sm3">Submenu 3</a></li>
        </ul>
    </li>
    <li>
        <a href="#/home">Menu 2</a>
    </li> 

</ul>


// JavaScript 

$('.main-menu').dropdownMenu();

  - 위의 경우를 단순한 AngularJS의 Directive로 표현한다. 이렇게 하면 jQuery처럼 HTML과 Javascript를 분리할 필요가 없고 해당 <ul> HTML 태그가 dropdown-menu임을 바로 알 수 있다.  

<ul class="main-menu" dropdown-menu>
    ... 

</ul>

  - 애플리케이션 개발시 반은 jQuery 반은 AngularJS 스타일로 - half jQuery, half AngularJS - 절대로 절대로 개발하지 말라

  - 디자인을 하지 말고, Markup을 해라. 즉, 아키텍쳐링을 하고 그 다음에 디자인을 하라. 


3-2. Data Binding

  - DOM을 조작(첨부,삭제)하는데 있어서 Two Way Binding을 적극적으로 사용하라 

  - jQuery 의 경우를 보자 : append하고 delete 하는 조작들을 넣어야 한다. 

$.ajax({
  url: '/myEndpoint.json',
  success: function ( data, status ) {
    $('ul#log').append('<li>Data Received!</li>');
  

});

<ul class="messages" id="log"</ul>

  - AngularJS 를 보자 : $scope를 통하여 controller와 view 간의 데이터가 삭제 되거나 첨부되는 것을 보다 쉽고 깔끔하게 처리해 준다

// controller 코드
$http( '/myEndpoint.json' ).then( function ( response ) {
    $scope.log.push( { msg: 'Data Received!' } ); 

});


// html 코드 

<div class="messages">
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div> 

</div>


3-3. 모델객체와 화면과 분리되어 있다

  - $scope를 통하여 view와 controller간 모델역할을 수행한다 

  - 이 방법은 view와 완전 독립적이어서 separation of concern을 유지하고 테스트 가능하게 만들준다


3-4. 관심의 분리 (SoC, separation of concern)

  - 아키텍쳐링을 하여 개발하면 관심 영역별로 분리하여 유지할 수 있다

  - 화면(view)은 공식적인 표현되는 것이고, 모델(model)은 데이터를 대표하며, 업무를 수행하는 서비스를 갖게된다 

  - 이것은 3-5에서 이야기하는 DI와도 연관이 있다 


3-5. 의존성 주입 (DI, Dependency Injection)

  - 코드안의 하드코딩된 부분을 제거하고 의존관계에 있는 모듈을 다양한 방법으로 전달해 준다

  - AngularJS에서는 펑션의 파라미터로 넘겨준다. 이를 통해 테스트시에 서버를 직접 호출할 필요없이 필요한 목서비스(mock service)를 전달하여 테스트를 할 수도 있다 



4. 항시 TDD 하라 

  - Test Driven Development 를 수행하라

  - jQuery로 테스트 할 때는 항시 테스트 화면을 만들어 해야 제대로 된 테스트 수행이 가능해진다. 그러나 AngularJS는 그렇지 않다

  - 내부에 사용되는 기능을 모듈로 분리해 개발하고 DOM조작에 대해서 테스트하고 애플리케이션과 통합할 수가 있기 때문이다 

  - 그것은 관심의 분리(SoC) 방식으로 개발하기 때문에 TDD가 AngularJS에서 가능한 것이다 

  - 예를 보자 

// HTML : when-active라는 AngularJS Directive(지시자)

<a href="/hello" when-active>Hello</a>

// Test Script : Karma를 보자. DOM을 조작하여 화면을 이동시킨 후 true, false인지 체크한다 

it( 'should add "active" when the route changes', inject(function() {
    var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );

    $location.path('/not-matching');
    expect( elm.hasClass('active') ).toBeFalsey();

    $location.path( '/hello' );
    expect( elm.hasClass('active') ).toBeTruthy();
}));


// Script : 정상적으로 라우팅이 되면 active class 첨부, 아니면 삭제 

.directive( 'whenActive', function ( $location ) {
    return {
        scope: true,
        link: function ( scope, element, attrs ) {
            scope.$on( '$routeChangeSuccess', function () {
                if ( $location.path() == element.attr( 'href' ) ) {
                    element.addClass( 'active' );
                }
                else {
                    element.removeClass( 'active' );
                }
            });
        }
    };
});



5. Directive(지시자)는 패키징된 jQuery가 아니다 

  - Directive안에서 DOM 조작을 하라. 하지만 jQuery 스타일은 배제하라 

  - Directive는 template을 가지고 있는 widget과 유사하다. 이를 통해 SoC를 보여주고 있고, 테스트를 가능하게 한다 

  - jQuery 스타일의 버튼토글 코딩을 보자 : AngularJS Directive안에 아래와 같이 jQuery 스타일의 DOM 조작을 수행하지 말라

.directive( 'myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            var on = false;

            $(element).click( function () {
                if ( on ) {
                    $(element).removeClass( 'active' );
                }
                else {
                    $(element).addClass( 'active' );
                }

                on = !on;
            });
        }
    }; 

});

  - Angular Way 방식의 버튼코글 : ng-show, ng-class, ng-binding 과 같은 Directive를 통해 DOM 조작을 수행하라  

.directive( 'myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            scope.on = false;
            scope.toggle = function () {
                scope.on = !$scope.on;
            };
        }
    }; });

  - Directive는 jQuery같은 펑션의 집합이 아니다. Directive는 HTML의 확장이다. 이는 HTML안에 우리가 수행하고 싶은 무언가를 넣고 싶은데 못 할 경우에 Directive를 만들어야 하는 것이다. 그래서 HTML의 일부로서 사용하는 것이다. 



Angular Way에서 jQuery 방식을 사용하지 말고 포함조차 시키지 말아라. 


- View  === AngularJS의 Directive가 포함된 HTML이다 

- Model === $scope를 통하여 Controller에서 Two way binding을 자동 수행한다 

- View와 Controller를 통하여 표현과 행위를 분리한다 (SoC)

- AngularJS 애플리케이션 접근법

   Step 1) Model을 먼저 생각하고 Service를 만든다

   Step 2) Model이 표현될 View에 대해 생각해 보고 template을 만들고, 필요하면 자동 모델 바인딩되는 Directive도 만든다.

   Step 3) 각 View 를 Controller에 연결한다 (ng-view and routing, ng-controller)



<참조>

  - 원문 : Think in AngularJS

  - AngularJS의 상속에 대한 이해 (그림포함)

posted by 윤영식
2013. 8. 20. 17:32 AngularJS/Prototyping

iCheck 플러그인은 HTML input의 type이 Checkbox나 Radio 의 모양을 UX 입장에서 직관적으로 만들어 준다. AngularJS하에서 이런 JQuery Plugin을 사용하려면 Directive(지시자) 화하여 사용해야 하는데 어떻게 만드는지 알아보자 




1. Directive 만드는 형식 

  - 사용하려는 HTML 안에서 

<div directiveName ></div>

<script type="text/javascript" src="pluginName.js"></script>


  - AngularJS Directive(지시자) 만들기

    + pluginActivationFunction은 플러그인의 호출하는 펑션이다. 

    + scope.$eval 은 플러그인의 환경값을 넣어주면 된다 

App.directive('directiveName', function() {

    return {

        restrict: 'A',

        link: function(scope, element, attrs) {

            $(element).'pluginActivationFunction'(scope.$eval(attrs.directiveName));

        }

    };

}); 



2. iCheck Directive 만들기 

  - ng-app 모듈에 Directive를 만든다 

    + Radio 박스 선택에 따른 부가화면을 show하거나 hidden 하는 처리를 한다  

    + Attribute로 ngIcheck 지시자를 만들었다. (HTML에서는 ng-icheck 로 사용된다)

    + ifChecked의 iCheck 이벤트를 받았다. iCheck의 Radio를 사용하면서 AngularJS의 Radio 이벤트를 받을 수 없게 되었기 때문이다 

    + Radio의 attribute로 value="file" or "direct" 라는 두개의 Radio 선택 옵션을 주었다. 

    + AngularJS Context안에서 $scope의 값 변경을 인식하기 위하여 $scope.$apply()를 iCheck.on listener의 핸들러 펑션에 호출함.

    + $(element).iCheck(<환경값>); 의 plugin 초기화 펑션을 호출해 주었다 

.directive('ngIcheck', function() {

   return {

       restrict: 'A',

       controller: function($scope, $element) { 

        $element.on('ifChecked', function(event){

 if(event.target.value === 'file') {

  $scope.isFile = true;

  $scope.isDirect = false;

  $scope.$apply();

 } else if(event.target.value === 'direct') {

  $scope.isDirect = true;

  $scope.isFile = false;

  $scope.$apply();

 } else {

  $scope.isFile = false;

  $scope.isDirect = false;

  $scope.$apply();

 }

});

       },

       link: function(scope, element, attrs) {

           $(element).iCheck({

        checkboxClass: 'icheckbox_square-blue', 

        radioClass: 'iradio_square-blue', 

        increaseArea: '20%' 

       });

       }

   };

});


// 중요)

// iCheck를 AngularJS의 Directive로 하지 않고 사용하려면 HTML하단에 해당 script를 넣어준다

// 이렇게 하면 Radio 버튼의 이벤트가 AngularJS Context안에서 작동하지 않기 때문에 AngularJS의 기능을 사용할 수 없다 

// 결국 이렇게 사용하지 않기 위해선 JQuery Plugin을 AngularJS Directive로 만들어 AngularJS Context에서 돌아가게 해주는 것이다

<script>

$(document).ready(function(){

  $('input').iCheck({

    checkboxClass: 'icheckbox_square-blue',

    radioClass: 'iradio_square-blue',

    increaseArea: '20%'

  });

});

</script>


  - HTML에서 사용하기 

    + <input type="radio" name="iCheck" .../> : 여기까지는 iCheck에서 사용함 

    + <input ... value="file" ng-icheck> : AngularJS에서 사용. ng-icheck Attribute를 만다면 AngularJS가 컴파일하고 link하고 이벤트 반응에 대하여 controller에서 처리한다 

    + Radio의 선택에 따라서 ng-show="isFile", ng-show="isDirect"에 따라서 해당 div가 show, hidden 되어 진다 

  <table id="maForm" class="table table-striped table-bordered">

      <thead>

        <tr>

          <th width="25%"><i class="icon-check-sign icon-small"></i> Options</th>

          <th width="75%" class="not-for-mobile"><i class="icon-edit icon-small"></i> Insert Data</th>

        </tr>

      </thead>

      <tbody>

        <tr>

          <td>

            <label>Select Input Method</label> <br>

            <table class="table table-condensed">

              <tr class="active">

                  <td><input type="radio" name="iCheck" value="file" ng-icheck /></td>

                  <td>Upload File</td>

              </tr>

              <tr class="active">

                  <td><input type="radio" name="iCheck" value="direct" ng-icheck /></td>

                  <td>Input Direct</td>

              </tr>

            </table>

          </td>

          <td>

          <!-- input direct data -->

          <div ng-show="isDirect">

  <textarea ng-model="ma.direct" class="form-control" rows="12"></textarea>

  </div>

  <!-- upload file such as .csv -->

  <div ng-show="isFile">

                  <input id="fileupload" ng-model="ma.file" type="file" name="files[]" multiple>

  </div>

          </td>

        </tr>

        <tr>

      <td colspan="2">

  <i class="icon-bar-chart icon-small"></i> Moving Average Chart<br>

  <img src="http://www.placehold.it/880x300/EFEFEF/AAAAAA&amp;text=no+image">

      </td>

        </tr>

      </tbody>

    </table>


  - 결과 : 선택하는 옵션 박스가 iCheck 플러그인을 통하여 좀 더 직관적으로 나오게 되었다

    + 단순한 Radio 옵션

    

    + 변경된 iCheck Radio 옵션

    



<참조> 

  - Correct way to integrate JQuery Plugins in AngularJS 

  - How to convert a jQuery plugin to AngularJS Directive : 유튜브 동영상

  - Directive의 $watch 와 $apply 완벽 이해 : $apply, $digest, $watch 에 대한 상세 설명

  - Thinking in AngularJS : Angular Way 

  - SlideJS Plugins을 Directive로 만드는 유사한 예제

'AngularJS > Prototyping' 카테고리의 다른 글

[AngularJS] Twitter Search 만들기  (0) 2013.04.05
[AngularJS] Todo 목록 만들기  (0) 2013.04.05
[AngularJS] Hello World 만들기  (0) 2013.04.04
posted by 윤영식
2013. 4. 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 윤영식
2013. 4. 10. 17:35 AngularJS/Concept

애귤러군은 HTML을 어떻게 컴파일할까 알아보자 



1) 개요

  - HTML element 또는 attribute를 확장할 수 있게 해준다. 즉, 사용자 정의하여 새로운 HTML syntax를 브라우징할 수 있게 한다

  - Angular가 해석하는 Behavior Extension 을 directive 라고 한다 

  - Angular는 미리 번들링된 directives 들도 있지만 사용자가 만들 수도 있다 

  - 지시자의 해석은 서버에서도 미리 컴파일 되는 것도 아니다. 컴파일은 그냥 웹브라우저에서 수행한다  



2) 컴파일러

  - Compiler는 앵귤러 서비스이다 

  - 컴파일시 two phases

    + Compile : DOM/directives 해석

    + Link : scope를 가지고 directive를 결합시키고, 실시간 화면을 만든다. 모델이 변하면 뷰도 변하도록 바인딩한다

    예) ng-repeat : 성능향상을 위해 복제된 템플릿은 한번의 Compile이 필요하고, 각 복제된 인스턴스별로 한번 Link된다  



3) 지시자

  - 컴파일 과정에서 HTML 생성시 정의한 것과 매칭시키기 위한 행위자(Behavior) 이다. 

  - element name, attribute, class name, comment 에 놓일 수 있다

  - span 앨리먼트의 애트리뷰트로 사용한 draggable

  1. <!doctype html>
  2. <html ng-app="drag">
  3. <head>
  4. <script src="http://code.angularjs.org/1.0.5/angular.min.js"></script>
  5. <script src="script.js"></script>
  6. </head>
  7. <body>
  8. <span draggable>Drag ME</span>
  9. </body>
  10. </html>



4) 뷰와 모델 바인딩

  - 일반적인 경우 

    + 데이터 변화면 다시 template과 model을 합쳐서 DOM안의 innerHTML 로 보여줘야 함

  



  - 앵귤러 경우

    + Angular compile가 지시자를 가지고 있는 DOM 을 템플릿으로 사용, linking function을 통해서 scope model 과 live view 바인딩을 해준다

    + 별도의 innerHTML 입력이나 이벤트 핸들링등의 코드가 전혀 필요없다 

  


<참조>

  - 원문 : Developer Guide - HTML compiler

posted by 윤영식
prev 1 next