블로그 이미지
Peter Note
Web & LLM FullStacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

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 Peter Note
2015. 1. 5. 20:25 AngularJS/Concept

AngularJS v1.3에서 많은 기능들이 새롭게 추가되었지만 우선 성능에 영향을 미쳤던 부분을 해결하는 방법을 알아보자. 



양방향 데이터 바인딩 이해


  AngularJS의 특징중 양방향 데이터 바인딩은 reactive programming의 구현체라고 할 수 있다. Meteor의 Blaze, Facebook의 React도 같은 기능을 제공한다. 이들은 HTML과 JS사이에 특별한 코드의 등록없시도 HTML->JS / HTML <- JS 양방향으로 데이터 갱신이 가능하다. AngularJS에서 양뱡향 데이터 바인딩의 중요 매개체는 $scope이고 해당 객체 내부에는 데이터의 변경을 감시하는 $watch가 있는데, 모든 개별 데이터마다 $watch가 등록된다. 하기와 같이 $scope.name에 대한 expression도 watcher(감시자, 즉 $watch)가 등록된다. (예제)

<span> Hi {{name}}</span>


  name이 $scope에 속성으로 선언되어 HTML에서 사용을 하면(사용하지 않는 속성은 등록되지 않음) AngularJS는 $watch를 모두 등록하고 $scope 속성 하나라도 변경이 되면 Duty-Checking방식으로 AngluarJS Context안의 $digest 루프를 돌면서 매칭되는 $scope의 속성을 사용하는 DOM 내역을 동적으로 변경한다. 여기서 생각해 볼것은 만일 화면에 뿌려지는 $scope의 속성값이 100개이고(테이블이나 리스트 같은 경우 ng-repeat을 사용한다면) 최초 화면이 뿌려진 후 단 1개만이 사용자의 액션이나 실시간 Push를 통해 변경되는 것이어도 100의 $watch를 $digest루프를 돌며 변경을 체크하게 된다. 100개가 아니라 10만개라면 어떻게 될까? 분명 성능상에 영향을 미칠것이다. $watch, $digest에 대한 사항은 블로그를 읽어보자




Watch Expressions 성능 개선


  one-time expression은 최초 digest 루프를 한번 타고 값이 설정되면 그 다음에 다시 digest 루프를 타지 않게 해주는 것이다. 따라서 최초 동적 바인딩이 이루어진 후에는 $watch에서 제거되어 digest 루프를 타지 않도록 함으로 성능상의 이점을 누릴 수 있다. 사용방법은 간단히 $scope의 속성앞에 :: 를 하기와 같이 표기한다. (예제)

<span> Hi {{::name}}</span>


  이제 최초에 한번 데이터를 바인딩한 후에 동적 변경이 필요없는 HTML안의 AngularJS Expression들에 One-Time-Binding을 사용하자.




<참조> 


  - Explorering Angular v1.3 - One-Time-Binding

  - $watch, $digest는 무엇인가?

posted by Peter Note
2014. 8. 4. 00:41 AngularJS

  AngularJS(이하, 앵귤러) 기반의 싱글 페이지 웹 애플리케이션 개발에 대한 책을 집필하고 있다. 책안에 들어갈 내용을 위해 참조한 내역을 정리해 보고자 한다. 요즘 가장 큰 화두는 다양한 프론트 앤드 프레임워크가 나옴에 따라 프론트 앤드 개발에도 아키텍팅이 필요하다는 것이다. 프론트 앤드에서는 SPA(Single Page Application 이하, 싱글 페이지 웹 애플리케이션)가 앵귤러 프레임워크 기반으로 많이 소개 되고 있다. 싱글 페이지 웹 애플리케이션 접근 방식은 JSP, PHP같은 서버 템플릿이나 스프링 프레임워크에 익숙한 개발자에게는 생소한 개념이다. 


  싱글 페이지 웹 애플리케이션(SPA)은 "싱글 페이지 웹" 글자만 빼면 그냥 애플리케이션이다. 윈도우에 설치해서 사용하는 .exe 애플리케이션 개발 방식과 하등 차이가 없고 보여지는 기반이 브라우저안이라는 것 밖에 없다. 몇년전 리치 인터넷 애플리케이션(RIA)를 이끌었던 플랙스(Flex)를 떠올려도 좋다. 하지만 지금은 모바일과 웹의 시대이니 HTML/CSS/JavaScript 만으로도 충분히 애플리케이션을 만들 수 있어야 한다. 


  앵귤러는 애플리케이션을 만드는 프레임워크이다. 스프링 MVC 패턴의 개발을 했다면 앵귤러에서도 그렇게 할 수 있다. 스프링 의존성 주입의 편리함을 누리고 있다면 앵귤러에서도 그렇게 할 수 있다. 이제 웹의 세상에 맞는 애플리케이션 개발자로 거듭나 보자. 






목차 


  1장. 싱글 페이지 애플리케이션 개발 준비

    + 1-1 개발 도구 준비

    + 1-2 싱글 페이지 애플리케이션 생성

    + 1-3 애플리케이션 컴포넌트 생성

    + 1-4 애플리케이션 테스트 및 빌드빌드



  2장. AngularJS 프레임웍 이해

    + 2-1 MV* 프레임웍

    + 2-2 양방향 데이터 바인딩

    + 2-3 의존성 주입

    + 2-4 클라이언트 템플릿

    + 2-5 지시자 (Directive)

    + 2-6 테스트 프레임웍

    


  3장. 싱글 페이지 애플리케이션 기획 및 생성

    + 3-1 애플리케이션 기획

    + 3-2 애플리케이션 제너레이터 생성

    + 3-3 싱글 페이지 애플리케이션 생성

    + 3-4 단위 업무를 위한 앵귤러 컴포넌트 조합



  4장. 애플리케이션을 위한 공통 프레임웍 개발

    + 4-1 공통 프레임웍 모듈 개발

    + 4-2 로그인 프론트앤드 개발

    + 4-3 OAuth를 이용한 인증 처리

    


  5장. 메인 페이지 개발 

    + 5-1 백앤드 API 개발

    + 5-2 프론트앤드 화면 레이아웃

    + 5-3 그룹 목록 및 정보 표현 



  6장. 그룹 페이지 개발 

    + 6-1 그룹 정보 페이지

    + 6-2 그룹 활동 페이지 

    + 6-3 설문 카드 생성

    + 6-4 설문 카드 목록에 표현

    + 6-5 설문 응답 및 결과 표현



  7장. 실시간 반응 개발

    + 7-1 Socket.IO 기반 실시간 연동

    + 7-2 카드 표현 고도화

    + 7-3 AngularJS 성능 옵션



 총 7개의 목차 중에서 1, 2장을 출판사의 양해를 얻어 공개토록 한다. 

  

posted by Peter Note
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 Peter Note
2014. 8. 3. 23:26 AngularJS/Concept

AngularJS를 배우기 위해 좌충우돌 하며 읽고, 보고, 듣고, 코딩해본 코스를 나름 정리해 보았다. 




1. 개념잡기

  - Angular's father인 미스코님의 AngularJS 소개 동영상을 본다 : 단계별로 jQuery와 잘 비교하고 있다

    

  - Art of AngularJS를 보고서 이제 배워야할 필요성을 느껴보세요. 이제 시작하세요. 

    


  - AngularJS의 중요 요소와 기본기를 다져보자 (60분 동강)

    

     + AngularJS 전체 구성 요소 : 각각에 대해 간단히 알아보자

   

      + AngularJS 흐름

   


  - 개발자 가이드에서 Bootstrap, HTML Compiler, Conceptual Overview, Directives 를 꼭 읽는다 (내 포스팅에도 정리했다)

  - AngularJS 홈페이지에서 제공하는 튜토리얼 보기

  - 요약 정리 내역을 다시 보자




2. 프로토타입핑 해보기

  - 전체 flow를 간단히 코딩해 보자

  - egghead.io 에서 모든 동강을 따라해 본다 : 100개 넘는 강좌가 있고 짧은 대신 꼭 코딩해본다 

    

  - AngularJS 팀의 Meetup 동영상들 보기 : Angular 기능별 설명을 해준다 (이건 개당 1시간씩 넘어서 틈틈히 보자)

    + Best Practices :  미스코님이 직접 설명한다.

    

  - AngularJS에서의 Deferred and Promise에 대한 이해

    

  - http://www.yearofmoo.com/ 의 AngularJS 강좌들 코딩

    + Use AngularJS to Power

    + More AngularJS Magic

  - AngularJS Design Patterns 

  - MEAN Stack 사용 예제 : MEAN is MongoDB + Express + AngluarJS + Node.js




3. Angular 생태계

  - Angular-UI : Twitter Bootstrap, Grid 등 지속적으로 AngularJS Directives 를 만들어 내고 있다 

  - angular modules는 http://ngmodules.org/ 먼저 찾아보자

    

  - angular-strap : http://mgcrea.github.io/angular-strap/

  - Learn AngluarJS : Thinkster.io

  - AngularJS-Tips




4. 데이터 연동

  - AngularJS에서 제공하는 $resource를 사용하여 RESTful 하게 개발한다

    + 동영상에서 $resource를 어떻게 사용하는지 익히자 (GitHub Demo 소스)

    

  - RestangularJS를 이용한  RESTful 서버 연동

  - Real-time을 위한 AngularJS와 Socket.io 연동하기 seed 구성




5. 개발환경 구축하기 

  - 개발 소스 코드 저장소는 DVCS인 Git + GitHub 을 사용한다 : Git 을 통하여 성공적인 브랜칭 전략을 세우자

  - Yeoman을 통하여 AngularJS의 스케폴딩 소스를 만들어 개발한다 : AngularJS 개발환경 구성하기

  - Yeoman generator Angular 와 Express.js 연동하기




6. 참여와 공유

  - AngularJS 구글 그룹스 참여

  - 공개 그룹 활동하기

    + Facebook AgularJS 오픈 그룹 

    + Google+ AngularJS 오픈 그룹 

  - JSFiddle을 통하여 예제파일 공유하기

  - StackOverflow 통하여 Q&A 하기

  - AngularJS 트위터 팔로잉

최근 -213.10.11- 으로 AngularJS에 대한 관심이 높아지고 있고, 다양한 블로그와 관련 글이 많이 올라오고 있다. 이중 AngularJS 배우기의 종결자 블로깅을 들어가서 배워보자 



7. AngularJS v1.* 에서 v2.* 으로  
  - AngularJS는 올해 v2.* 버전이 나올 예정이고 큰 변화를 예고 하고 있다. 하지만 이것은 피할 수 없는 대세로 보여진다. 이유는 간단하다. 좀 더 단순하고 좀 더 성능이 뛰어나며 인지 가능한 단순 문법으로 하는 길로 가고 있다. 
  - v1.* 에서 v2.* 로 바뀌니 어쪄냐고 호들갑 떨지 말자. v1.*도 쓰지 않으면서 v2.* 의 변화를 논하지도 말자. 지금 당장 v1.*를 써보고 좀 더 쉽게 웹 애플리케이션을 개발해 보는 경험이 중요하다. v2.*가 성숙할 때 쯤이면 우리는 더 많은 변화 앞에 놓여 있을 것이다. 
  - v2.* 간결함과 Web Components, ECMAScript 6 로의 방향의 핵심은 Componen를 사용한 Composition 패턴의 애플리케이션이라 보며 새롭지도 않으며 어렵지도 않은 방향으로 대중적으로 사용할 수 있게 만들어 가는 능력에 찬사를 보낸다. 땡큐 AngularTeam!!!
  
  



8. Directives $compile 서비스에 대한 이해 

2015 ng vegas에서 발표한 $compile에 대한 구현과정을 상세히 설명하고 있다. 예로 대시보드 빌더를 개발한다고 했을 때 대시보드 화면에 차트를 가져다 놓을 때 차트에 대한 디렉티브가 있다고 하면 동적으로 디렉티브를 컴파일하고 $scope와 링크를 맺우어주어야 한다. 이런 일련의 과정이 어떻제 진행되는지 알 수 있다. 또한 디렉티브에 대한 상호 관계로 상속(Inheritance)와 격리(Isolate)부분도 설명한다.  

- 발표 동영상 

  
  
  발표중에 나온 장표 

  




<참조>

  - 배우는 과정을 잘 요약한 블로깅 : http://blog.whydoifollow.com (강추)

  - AngularJS 내용 요약 (필독)

  - Angular v1.3 변경 사항들

  - 몇가지 참조할 만한 사이트 

    + AngularJS 포스팅 in codingsmackdown.tv

       tiny -> medium -> large project 확장하는 방법 (소스)

    + 페이지 플래그먼트의 라우팅 : http://onehungrymind.com/building-a-website-with-angularjs-routes-and-partials/

    + $apply 사용 이유http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

    + $provide 사용 : http://slides.wesalvaro.com/20121113/#/

    + AngularJS에서 Dependency Injection에 대한 이해

    + 요분 유명하다 와인샐러 샘플 : http://coenraets.org/blog/2012/02/sample-application-with-angular-js

    + 전체 구성요소 설명

    + Directive 통하여 재사용가능 컴포넌트 만들기

    + Lazing Loading in AngularJS

    + Animation in AngularJS

    + AngularJS CheetSheet

  - 처음 개발할 때 8가지 유용한 개발 접근법 ($rootScope, $locationProvider 사용예)



posted by Peter Note
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 Peter Note
2014. 6. 4. 00:20 AngularJS/Concept

Angular.js 기반 개발시 시각화를 위하여 구글 차트를 이용할 경우 Directive화 방법




구글

  - Don't invent wheel, already exist

  - 하지만 자신의 입맛에 맞게 수정은 해야 한다. 

  - 수정하려면 GitHub에서 Fork한 후에 수정을 하자

    https://github.com/mobiconsoft/angular-google-chart-resize



구글 차트 API 사용법

  - 구글 차트 사용하기 

  - 구글 Visualization 라이브러리위에 구글 Chart가 올라가는 형태이다 (참조)

  - visualization의 버전 1.0 을 명시하고 사용할 차트 패키지를 설정한다. 보통 corechart 로 하면 bar, line, pie등을 사용할 수 있다

google.load('visualization', '1.0', {'packages':[<<list-of-google-chart-libraries>>]})


  - 데이터 생성은 2차원의 google.visualization.DataTable을 이용한다 

  - https://google-developers.appspot.com/chart/interactive/docs/datatables_dataviews

  - anuglar의 controller에서 하기와 같이 json형태로 정의 가능. 또는 addColumn() 호출 설정방식중 택일

  - DataTable을 통해 .csv 파일 생성가능 

$scope.chartObject = {};

$scope.onions = [

    {v: "Onions"},

    {v: 3},

];

// 데이터 시각화를 위하여 가장 큰 값은 첫번째에, 그 다음 큰값은 맨 마지막에 오게 한다 

$scope.chartObject.data = {"cols": [

    {id: "t", label: "Topping", type: "string"},

    {id: "s", label: "Slices", type: "number"}

], "rows": [

    {c: [

        {v: "Olives"},

        {v: 31}

    ]},

    {c: $scope.onions},

    {c: [

        {v: "Zucchini"},

        {v: 1},

    ]},

    {c: [

        {v: "Pepperoni"},

        {v: 2},

    ]},

    {c: [

        {v: "머쉬롬"},

        {v: 10},

    ]},

]};


$scope.chartObject.options = {

    'title'  : 'How Much Pizza I Ate Last Night',

    'height' : 500,

    'colors' : ['#e0440e', '#e6693e', '#ec8f6e', '#f3b49f', '#f6c7b6']

}



  - 차트 커스터마이징을 위하여 옵션을 준다

  - 높이는 px(pixel) 단위이다.

  - 차트의 width + height 주는 방법은 html에 주거나, option 으로 주는 두가지 방법이 있다. (참조)

var options = {

  'legend':'left',

  'title':'My Big Pie Chart',

  'is3D':true,

  'width':400,

  'height':300

}


  - ChartWrapper를 이용하면 차트 종류, 데이터, 옵션, 그려지는 위치등의 설정을 한번에 할 수 있다 (ChartWrapp API)

var wrapper = new google.visualization.ChartWrapper({

  chartType: 'ColumnChart',

  dataTable: [['', 'Germany', 'USA', 'Brazil', 'Canada', 'France', 'RU'],

              ['', 700, 300, 400, 500, 600, 800]],

  options: {'title': 'Countries'},

  containerId: 'vis_div'

});

wrapper.draw();


  - Chart의 interaction은 ready, select, error, onmouseover/onmouseout 이 존재한다 (참조)

  - addListener를 이용하여 event handler를 등록한다 

function selectHandler() {

  var selectedItem = chart.getSelection()[0];

  if (selectedItem) {

    var topping = data.getValue(selectedItem.row, 0);

    alert('The user selected ' + topping);

  }

}


google.visualization.events.addListener(chart, 'select', selectHandler);  


  - Formatters 에는 arrow, bar, color, date, number 등이 있고, 주로 table에서 사용된다. 단, color는 차트에서도 사용됨 (참조)

  - Advanced Chart 기능을 쓰고 싶을 경우 (참조)

  - 웹툴상에서 차트의 API 테스트 하는 도구 



기존 소스 이해

  - google-chart에 대한 directive 소스에 resize 넣음 

/**

 * @description Google Chart Api Directive Module for AngularJS

 * @version 0.0.9

 * @author Nicolas Bouillon <nicolas@bouil.org>

 * @author GitHub contributors

 * @moidifier Peter Yun <nulpulum@gmail.com>

 * @license MIT

 * @year 2013

 */

(function (document, window, angular) {

    'use strict';


    angular.module('googlechart', [])


        .constant('googleChartApiConfig', {

            version: '1.0',

            optionalSettings: {

                packages: ['corechart']

            }

        })


        /**

         * 인코딩 주의)

         * index.html 안에 UTF-8 설정

         * <meta http-equiv="content-type" content="text/html; charset=UTF-8">

         *

         * 사용예)

         * <script type="text/javascript" src="https://www.google.com/jsapi"></script>

         * <script type="text/javascript">

         *   google.load('visualization', '1.0', {'packages':['corechart']});

         *   google.setOnLoadCallback(drawChart);

         *   function drawChart() { ... }

         * </script>

         *

         * @변경 : jsapi.js 파일을 로컬에서 읽어오도록 수정할 수 있다

         * googleJsapiUrlProvider.setProtocol(undiefined);

         * googleJsapiUrlProvider.setUrl('jsapi.js');

         *

         * @참조 : https://google-developers.appspot.com/chart/interactive/docs/quick_start

         */

        .provider('googleJsapiUrl', function () {

            var protocol = 'https:';

            var url = '//www.google.com/jsapi';


            this.setProtocol = function(newProtocol) {

                protocol = newProtocol;

            };


            this.setUrl = function(newUrl) {

                url = newUrl;

            };


            this.$get = function() {

                return (protocol ? protocol : '') + url;

            };

        })


        /**

         * google.load('visualization', '1.0', {'packages':['corechart']}); 수행하는 팩토리

         *

         * @param  {[type]} $rootScope     [description]

         * @param  {[type]} $q             [description]

         * @param  {[type]} apiConfig      [description]

         * @param  {[type]} googleJsapiUrl [description]

         * @return {[type]}                [description]

         */

        .factory('googleChartApiPromise', ['$rootScope', '$q', 'googleChartApiConfig', 'googleJsapiUrl', function ($rootScope, $q, apiConfig, googleJsapiUrl) {

            var apiReady = $q.defer();

            var onLoad = function () {

                // override callback function

                var settings = {

                    callback: function () {

                        var oldCb = apiConfig.optionalSettings.callback;

                        $rootScope.$apply(function () {

                            apiReady.resolve();

                        });


                        if (angular.isFunction(oldCb)) {

                            oldCb.call(this);

                        }

                    }

                };


                settings = angular.extend({}, apiConfig.optionalSettings, settings);


                window.google.load('visualization', apiConfig.version, settings);

            };

            var head = document.getElementsByTagName('head')[0];

            var script = document.createElement('script');


            script.setAttribute('type', 'text/javascript');

            script.src = googleJsapiUrl;


            if (script.addEventListener) { // Standard browsers (including IE9+)

                //console.log('>>> 1. load jsapi...');

                script.addEventListener('load', onLoad, false);

            } else { // IE8 and below

                //console.log('>>> 2. load jsapi...');

                script.onreadystatechange = function () {

                    if (script.readyState === 'loaded' || script.readyState === 'complete') {

                        script.onreadystatechange = null;

                        onLoad();

                    }

                };

            }


            head.appendChild(script);


            return apiReady.promise;

        }])


        /**

         * Element 또는 Attribute로 사용할 수 있다. Element 사용시에는 IE8+에 대한 고려가 있어야 함

         *

         * 사용예)

         * <div google-chart chart="chartObject" style="height:300px; width:100%;"></div>

         *

         * @param  {[type]} $timeout              [description]

         * @param  {[type]} $window               [description]

         * @param  {[type]} $rootScope            [description]

         * @param  {[type]} googleChartApiPromise [description]

         * @return {[type]}                       [description]

         */

        .directive('googleChart', ['$timeout', '$window', '$rootScope', 'googleChartApiPromise', function ($timeout, $window, $rootScope, googleChartApiPromise) {

            return {

                restrict: 'EA',

                scope: {

                    chartOrig: '=chart',

                    onReady: '&',

                    select: '&'

                },

                link: function ($scope, $elm, attrs) {

                    // Watches, to refresh the chart when its data, title or dimensions change

                    $scope.$watch('chartOrig', function () {

$scope.chart = angular.copy($scope.chartOrig);

                        drawAsync();

                    }, true); // true is for deep object equality checking


                    // Redraw the chart if the window is resized

                    $rootScope.$on('resizeMsg', function () {

                        $timeout(function () {

                            // Not always defined yet in IE so check

                            if($scope.chartWrapper) {

                                drawAsync();

                            }

                        });

                    });


                    // Keeps old formatter configuration to compare against

                    $scope.oldChartFormatters = {};


                    function applyFormat(formatType, formatClass, dataTable) {

                        var i, cIdx;


                        if (typeof($scope.chart.formatters[formatType]) != 'undefined') {

                            if (!angular.equals($scope.chart.formatters[formatType], $scope.oldChartFormatters[formatType])) {

                                $scope.oldChartFormatters[formatType] = $scope.chart.formatters[formatType];

                                $scope.formatters[formatType] = [];


                                if (formatType === 'color') {

                                    for (cIdx = 0; cIdx < $scope.chart.formatters[formatType].length; cIdx++) {

                                        var colorFormat = new formatClass();


                                        for (i = 0; i < $scope.chart.formatters[formatType][cIdx].formats.length; i++) {

                                            var data = $scope.chart.formatters[formatType][cIdx].formats[i];


                                            if (typeof(data.fromBgColor) != 'undefined' && typeof(data.toBgColor) != 'undefined')

                                                colorFormat.addGradientRange(data.from, data.to, data.color, data.fromBgColor, data.toBgColor);

                                            else

                                                colorFormat.addRange(data.from, data.to, data.color, data.bgcolor);

                                        }


                                        $scope.formatters[formatType].push(colorFormat)

                                    }

                                } else {


                                    for (i = 0; i < $scope.chart.formatters[formatType].length; i++) {

                                        $scope.formatters[formatType].push(new formatClass(

                                            $scope.chart.formatters[formatType][i])

                                        );

                                    }

                                }

                            }


                            //apply formats to dataTable

                            for (i = 0; i < $scope.formatters[formatType].length; i++) {

                                if ($scope.chart.formatters[formatType][i].columnNum < dataTable.getNumberOfColumns())

                                    $scope.formatters[formatType][i].format(dataTable, $scope.chart.formatters[formatType][i].columnNum);

                            }


                            //Many formatters require HTML tags to display special formatting

                            if (formatType === 'arrow' || formatType === 'bar' || formatType === 'color')

                                $scope.chart.options.allowHtml = true;

                        }

                    }


                    function draw() {

                        // 그리고 있는중에는 다시 그리기 안함

                        if (!draw.triggered && ($scope.chart != undefined)) {

                            draw.triggered = true;

                            // ref: https://docs.angularjs.org/api/ng/service/$timeout

                            $timeout(function () {

                                if (typeof($scope.formatters) === 'undefined')

                                    $scope.formatters = {};


                                var dataTable;

                                if ($scope.chart.data instanceof google.visualization.DataTable)

                                    dataTable = $scope.chart.data;

                                else if (angular.isArray($scope.chart.data))

                                    dataTable = google.visualization.arrayToDataTable($scope.chart.data);

                                else

                                    dataTable = new google.visualization.DataTable($scope.chart.data, 0.5);


                                if (typeof($scope.chart.formatters) != 'undefined') {

                                    applyFormat("number", google.visualization.NumberFormat, dataTable);

                                    applyFormat("arrow", google.visualization.ArrowFormat, dataTable);

                                    applyFormat("date", google.visualization.DateFormat, dataTable);

                                    applyFormat("bar", google.visualization.BarFormat, dataTable);

                                    applyFormat("color", google.visualization.ColorFormat, dataTable);

                                }


                                var customFormatters = $scope.chart.customFormatters;

                                if (typeof(customFormatters) != 'undefined') {

                                    for (var name in customFormatters) {

                                        applyFormat(name, customFormatters[name], dataTable);

                                    }

                                }

// resize > 0 이상이면 적용됨  

// by peter yun

 // <div style="height:100%" id="gc"

 // <div google-chart chart="chartObject" resize="#gc" style="width:100%;"></div>

 // </div>

 var height = angular.element(attrs.resize).height();

       if(height) {

$scope.chart.options.height = height;

       }


                                var chartWrapperArgs = {

                                    chartType: $scope.chart.type,

                                    dataTable: dataTable,

                                    view: $scope.chart.view,

                                    options: $scope.chart.options,

                                    containerId: $elm[0]

                                };


                                $scope.chartWrapper = new google.visualization.ChartWrapper(chartWrapperArgs);

                                google.visualization.events.addListener($scope.chartWrapper, 'ready', function () {

                                    $scope.chart.displayed = true;

                                    $scope.$apply(function (scope) {

                                        scope.onReady({chartWrapper: scope.chartWrapper});

                                    });

                                });

                                google.visualization.events.addListener($scope.chartWrapper, 'error', function (err) {

                                    console.log("Chart not displayed due to error: " + err.message + ". Full error object follows.");

                                    console.log(err);

                                });

                                google.visualization.events.addListener($scope.chartWrapper, 'select', function () {

                                    var selectedItem = $scope.chartWrapper.getChart().getSelection()[0];

                                    $scope.$apply(function () {

                                        $scope.select({selectedItem: selectedItem});

                                    });

                                });



                                $timeout(function () {

                                    $elm.empty();

                                    $scope.chartWrapper.draw();

draw.triggered = false;

                                });

                            }, 0, true);

                        }

                    }


                    /**

                     * jsapi의 load가 끝나면 draw하도록 promise를 걸어 놓은 것

                     *

                     * @return {[type]} [description]

                     */

                    function drawAsync() {

                        googleChartApiPromise.then(function () {

                            draw();

                        })

                    }

                }

            };

        }])


        /**

         * window즉 브라우져의 사이즈가 변경되면 google chart의 size를 변경토록 한다. 단 html설정에서 다음과 같이 되어야 한다

         * <div google-chart chart="chartObject" style="height:300px; width:100%;"></div>

         * height는 %가 아니라 자신이 속한 parent dive의 height를 따르도록 해주어야 한다

         *

         * @param  {[type]} $rootScope [description]

         * @param  {[type]} $window    [description]

         * @return {[type]}            [description]

         */

        .run(['$rootScope', '$window', function ($rootScope, $window) {

            angular.element($window).bind('resize', function () {

                $rootScope.$emit('resizeMsg');

            });

        }]);


})(document, window, window.angular);



<참조>

  - 구글 차트 Directive

  - 구글 차트 Directive 만들기 예제

  - 웹툴상에서 차트의 API 테스트 하는 도구 

  - Directive 수정 소스 

posted by Peter Note
2014. 6. 3. 15:23 AngularJS/Start MEAN Stack

Angular Team에서 End-To-End 테스트를 위하여 별도로 개발한 프레임워크가 Protractor 이다. protractor는 WebDriverJs기반으로 만들어 졌다. 





Protractor 설정

  - Chrome 최신 버전 사용

  - protractor 설치 및 Chrome에서 selenium standalone server로 구동하기 위한 driver도 설치한다 

$ npm install -g protractor

$ webdriver-manager update 


  - selenium server를 기동하는 방법 : 4444 port로 listen한다 ( 호출 :  http://localhost:4444/wd/hub )

$ webdriver-manager start

seleniumProcess.pid: 55938

6월 02, 2014 5:08:16 오후 org.openqa.grid.selenium.GridLauncher main

정보: Launching a standalone server

Setting system property webdriver.chrome.driver to /usr/local/lib/node_modules/protractor/selenium/chromedriver

17:08:16.913 INFO - Java: Oracle Corporation 24.51-b03

17:08:16.915 INFO - OS: Mac OS X 10.9.3 x86_64

17:08:16.935 INFO - v2.41.0, with Core v2.41.0. Built from revision 3192d8a

17:08:17.079 INFO - Default driver org.openqa.selenium.ie.InternetExplorerDriver registration is skipped: registration capabilities Capabilities [{platform=WINDOWS, ensureCleanSession=true, browserName=internet explorer, version=}] does not match with current platform: MAC

17:08:17.145 INFO - RemoteWebDriver instances should connect to: http://127.0.0.1:4444/wd/hub

17:08:17.146 INFO - Version Jetty/5.1.x

17:08:17.148 INFO - Started HttpContext[/selenium-server/driver,/selenium-server/driver]

17:08:17.149 INFO - Started HttpContext[/selenium-server,/selenium-server]

17:08:17.149 INFO - Started HttpContext[/,/]

17:08:17.510 INFO - Started org.openqa.jetty.jetty.servlet.ServletHandler@4b8e282c

17:08:17.510 INFO - Started HttpContext[/wd,/wd]

17:08:17.517 INFO - Started SocketListener on 0.0.0.0:4444

17:08:17.517 INFO - Started org.openqa.jetty.jetty.Server@3e578b06


  - 잘 동작하지 않는다면 Path에 JAVA_HOME을 설정한다. 



Protractor 환경 설정

  - 루트에 protractor-e2e.conf.js 파일 생성

  - 설정 : baseUrl은 grunt serve 시에 기동되는 port 번호

exports.config = {

  seleniumAddress: 'http://localhost:4444/wd/hub',

  capabilities: {'browserName': 'chrome'},

  specs: ['test/client/e2e/**/*.js'],

  baseUrl: 'http://localhost:9000',

  jasmineNodeOpts: {

    onComplete: null,

    isVerbose: true,

    showColors: true,

    includeStackTrace: true,

    defaultTimeoutInterval: 10000

  },

  framework: 'jasmine'

};

  - 체크

$ webdriver-manager start 

$ protractor protractor-e2e.conf.js

Using the selenium server at http://localhost:4444/wd/hub

[launcher] Running 1 instances of WebDriver


Finished in 0.001 seconds

0 tests, 0 assertions, 0 failures


[launcher] chrome passed

  - grunt를 이용하여 테스트 할 수 있도록 Gruntfile.js 에 설정. run: {..} 안의 options에 configFile 설정이 중요함 

$ npm install grunt-protractor-runner --save-dev


// grunt.initConfig 안에 설정

  protractor: {

      options: {

        keepAlive: true

      },

      run: {

        options: {

          configFile: "protractor-e2e.conf.js", // Target-specific config file

          args: {} // Target-specific arguments

        }

      }

  },


// task 설정

  grunt.registerTask('test', function(target) {

    if (target === 'server') {

      return grunt.task.run([

        'env:test',

        'mochaTest'

      ]);

    }


    else if (target === 'client') {

      return grunt.task.run([

        'concurrent:test',

        'autoprefixer',

        'karma',

        'protractor'

      ]);

    }


    else grunt.task.run([

      'test:server',

      'test:client'

    ]);

  });


// grunt test 에 첨부 

$ grunt test 또는 grunt test:client 수행 (단, 수행전에 webdriver-manager start 수행해 놓은 상태이어야함)


Running "protractor:run" (protractor) task

Using the selenium server at http://localhost:4444/wd/hub

[launcher] Running 1 instances of WebDriver


Finished in 0 seconds

0 tests, 0 assertions, 0 failures

[launcher] chrome passed

Done, without errors.


Execution Time (2014-06-02 15:43:51 UTC)

protractor:run  2.2s  ▇▇ 100%

Total 2.3s

  - 또는, grunt 설정을 하지 않았을 경우는 다음과 같이도 테스트 가능하다. 하지만 가급적 grunt를 이용하자.  

$ webdriver-manager start

$ protractor protractor-e2e.conf.js



End-to-End 테스트 코드 작성방식

  - 테스트 코드를 작성하기 위한 몇가지 개념을 익힌다. (위키)

> browser : webdriver를 감싼 객체. navigation 과 page information 을 갖음

> element : html element를 찾고 상호작용을 위한 helper method 

> by : element locator collection

> protractor : webdriver namespace를 wrapping한 protractor namespace


  - 샘플 코드 

    + browser.get : page loading 

    + element : page에서 element를 찾아줌 

    + by 종류 (소스 참조)

  • by.binding searches for elements by matching binding names, either from ng-bind or {{}} notation in the template.
  • by.model searches for elements by input ng-model.
  • by.repeater searches for ng-repeat elements. For example, by.repeater('phone in phones').row(11).column('price')returns the element in the 12th row (0-based) of the ng-repeat = "phone in phones" repeater with the binding matching{{phone.price}}.

describe('angularjs homepage', function() { it('should greet the named user', function() { // Load the AngularJS homepage. browser.get('http://www.angularjs.org'); // Find the element with ng-model matching 'yourName' - this will // find the <input type="text" ng-model="yourName"/> element - and then // type 'Julie' into it. element(by.model('yourName')).sendKeys('Julie'); // Find the element with binding matching 'yourName' - this will // find the <h1>Hello {{yourName}}!</h1> element. var greeting = element(by.binding('yourName')); // Assert that the text element has the expected value. // Protractor patches 'expect' to understand promises. expect(greeting.getText()).toEqual('Hello Julie!'); }); });


실제 End-to-End 테스트 코드

  - 로그인의 경우 e2e 코드 작성예 (필수참조)

'use strict';


var LoginPage = function() {

this.email = element(by.model('user.email'));

this.password = element(by.model('user.password'));


this.setEmail = function(email) {

this.email.sendKeys(email);

};


this.setPassword=  function(password) {

this.password.sendKeys(password);

};


this.url = function() {

browser.get('/#/signin');

};


this.login = function() {

return element(by.buttonText('button')).click();

};

};


describe('Login Page', function() {

it('should successfully login', function(){

var loginPage = new LoginPage();

loginPage.url();

loginPage.setEmail('test@test.com');

loginPage.setPassword('test');

loginPage.login().then(function(response){

console.log(response);

}, function(error){


})

});

});


  - 수행 : grunt test:client 

    + 수행 에러 발생함 : to be continue...



<참조>

  - Protractor 테스트 방법

  - Protractor Test PPT (필독)

  - Unit, E2E ToDo 예제

  - Protractor Grunt 설정하기

  - End-To-End Test 하기 예제

  - grunt-protractor-runner 

  - Angular Test Pattern

posted by Peter Note
2014. 6. 2. 10:09 AngularJS/Start MEAN Stack

프로트앤드 개발을 하면서 테스트는 어떻게 할 수 있는지 알아본다. 앵귤러에서는 테스트 관련 내용까지도 포함하고 있어서 개발 테스트의 편의성을 함께 제공하고 있다. 


사전 준비

  generator-angular를 설치하게 되면 앵귤러 프레임워크에 카르마(Karma) 테스트 프레임워크와 Mock 객체등이 포함되어 있다. 과거에는 Karma를 통해서 단위(Unit) 테스트와 E2E (End-To-End) 테스트 두가지를 하였으나, 최근에는 Karma로는 단위 테스트만을 수행하고 Protractor라는 새로운 테스트 프레임워크를 통해서 E2E 테스트를 수행한다 


  - karma.conf.js : Unit Test 환경파일. 내부적으로 Jasmine (http://pivotal.github.io/jasmine/) 프레임워크를 사용한다

  - karma-e2e.conf.js : E2E Test 환경파일 (

  - "grunt test" 를 통해 수행


Karma가 Jasmine 테스트 프레임워크를 사용하고, 필요한 files 목록을 정의하며 Chrome 브라우져에서 8080 포트를 사용하여 테스트함. angular.js 와 angular-mocks.js 파일은 필 첨부 (angular-mocks.js 안에 module, inject 펑션 존재)

// karma.conf.js

// Karma configuration

// http://karma-runner.github.io/0.10/config/configuration-file.html


module.exports = function(config) {

  config.set({

    // base path, that will be used to resolve files and exclude

    basePath: '',


    // testing framework to use (jasmine/mocha/qunit/...)

    frameworks: ['jasmine'],


    // list of files / patterns to load in the browser

    files: [

      'app/bower_components/angular/angular.js',

      'app/bower_components/angular-mocks/angular-mocks.js',

      'app/bower_components/angular-resource/angular-resource.js',

      'app/bower_components/angular-cookies/angular-cookies.js',

      'app/bower_components/angular-sanitize/angular-sanitize.js',

      'app/bower_components/angular-route/angular-route.js',

      'app/scripts/*.js',

      'app/scripts/**/*.js',

      'test/mock/**/*.js',

      'test/spec/**/*.js'

    ],


    // list of files / patterns to exclude

    exclude: [],


    // web server port

    port: 8080,


    // level of logging

    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG

    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes

    autoWatch: false,

    // Start these browsers, currently available:

    // - Chrome

    // - ChromeCanary

    // - Firefox

    // - Opera

    // - Safari (only Mac)

    // - PhantomJS

    // - IE (only Windows)

    browsers: ['Chrome'],


    // Continuous Integration mode

    // if true, it capture browsers, run tests and exit

    singleRun: false

  });

};


  Gruntfile.js 의 test Task 내역 : grunt test 

// Test settings

karma: {

   unit: {

      configFile: 'karma.conf.js',

      singleRun: true

  }

}


... 중략 ..

grunt.registerTask('test', [

    'clean:server',

    'concurrent:test',

    'autoprefixer',

    'connect:test',

    'karma'

  ]);



1. Jasmine 테스트 프레임워크 

  describe ~ it 으로 BDD를 표현한다. BDD는 TDD의 2세대 버전 정도로 이해하자. 보다 자세한 사항은 위키 참조 (http://en.wikipedia.org/wiki/Behavior-driven_development

 describe("A suite", function() {

  it("contains spec with an expectation", function() {

    expect(true).toBe(true);

  });

});


 describe로 시작하는 것을 Suite (슈트)라고 부르고, it 으로 시작하는 것을 spec (스펙)이라고 부른다. 슈트안에 여러개의 스펙이 있을 수 있다. beforeEach(), afterEach()를 통해서 스펙 수행시 마다 환경 초기값 셋업(Setup)이나 마무리 작업(TearDown)을 수행할 수 있다. 스펙 안에는 expect 기대값에 대한 다양한 메서드를 제공한다.

toEqual()

toBe(), not.toBe()

toBeTruthy(), toBeFalsy()

toContain(), not.toContain()

toBeDefined(), toBeUndefined()

toBeNull()

toBeNaN()

toBeGreaterThan()

toBeLessThan()

toBeCloseTo() : 소수점

toMatch() : 정규표현식

toThrow()

 ...


  보다 자세한 사항은 Jasmine 문서를 참조한다 (http://jasmine.github.io/2.0/introduction.html)



2. 앵귤러 Service 테스트 준비

  yo를 통하여 앵귤러 코드를 완성하게 되면 자동으로 테스트 코드까지 생성된다. SessionService에 대한 테스트 코드는 /test/spec/services 폴더 밑에 동일한 session-service.js 파일이 존재한다. module 호출 후에 inject 가 올 수 있으나 inject 후에 module 호출은 올 수 없다. _ (underscore)를 양쪽에 붙이는 것은 angular프레임워크에 테스트를 위하여 해당 서비스를 사용함을 알려준다. 

describe('Service: SessionService', function () {

  // load the service's module

  beforeEach(module('meanstackApp'));


  // instantiate service

  var SessionService;

  beforeEach(inject(function (_SessionService_) {

    SessionService = _SessionService_;

  }));


  it('should do something', function () {

    expect(!!SessionService).toBe(true);

  });


});

 

 grunt test 명령을 수행하면 에러가 발생할 것이다. 그것은 index.html에 필요에 의해 추가한 .js파일을 Karma 환경파일에 추가하지 않았기 때문이다. controller 쪽에서도 에러가 발생하는데 기본 테스트 코드에서 발생하므로 불필요한 코드를 삭제한다. 

 // karma.conf.js 

  files: [

      'app/bower_components/jquery/jquery.js',

      'app/bower_components/bootstrap/dist/js/bootstrap.js',

      'app/bower_components/angular/angular.js',

      'app/bower_components/angular-mocks/angular-mocks.js',

      'app/bower_components/angular-resource/angular-resource.js',

      'app/bower_components/angular-cookies/angular-cookies.js',

      'app/bower_components/angular-sanitize/angular-sanitize.js',

      'app/bower_components/angular-route/angular-route.js',

      'app/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',

      'app/bower_components/angular-ui-router/release/angular-ui-router.js',

      'app/scripts/*.js',

      'app/scripts/**/*.js',

      'test/mock/**/*.js',

      'test/spec/**/*.js'

    ],


  singleRun: true


// Controller 테스트 파일에서 불필요한 하기 코드 삭제 

  it('should attach a list of awesomeThings to the scope', function () {

    expect(scope.awesomeThings.length).toBe(3);

  });



3. 앵귤러 Service 테스트를 위한 Mock 사용

  SessionService 테스트 수행전에 공통적으로 사용하고 있는 Factory나 Service에 대한 테스트 코드를 작성한 후 SessionService를 최종 테스트 한다  


  - msRequestFactory : 원하는 JSON 객체가 생성되는지 테스트 

  - msRestfulApi : $httpBackend 이용하여 요청 처리 테스트

  - SessionService : $httpBackend 이용하여 요청 처리하고 SessionInfo의 정보 유무 테스트 


msRequestFactory 통하여 만들어진 JSON 객체가 원하는 key:value 값을 가지고 있는지 확인한다. expect().toEqual()을 사용한다 

 'use strict';


describe('Service: msRequestFactory', function () {


  // load the service's module

  beforeEach(module('meanstackApp'));


  // instantiate service

  var msRequestFactory;

  beforeEach(inject(function ($injector) {

    msRequestFactory = $injector.get('msRequestFactory');

  }));


  it('should exist', function () {

    expect(!!msRequestFactory).toBe(true);

  });


  it('should make JSON', function () {

    var request = msRequestFactory.createRequest('user', 'login', '');

    expect(request).toEqual({

      'area' : 'user',

      'resource' : 'login',

      'id' : '',

      'request' : {}

    });

  });


});

  

msRestfulApi는 $resource를 통해 서버에 요청을 보낸다. 서버 요청에 대한 Mock을 만들어서 응답을 줄 수 있는 기능으로 앵귤러는 $httpBackend를 제공한다. 즉, 실서버가 없을 경우 XHR 또는 JSONP 요청없이도 실서버에 요청한 것과 같은 응답을 줄 수 있다. $httpBackend의 유형은 두가지 이다


  - request expectation : 요청 JSON 데이터의 검증 원하는 결과가 왔는지 설정. expect().respond()

  - backend definition : 가상의 응답정보를  설정. when().respond()


서버의 요청은 모두 async 처리가 되므로 테스트코드 작성하기가 애매하다. 따라서 $httpBackend.flush()를 호출하는 시점에 request가 전달되므로, 테스트 코드 작성시 적절한 위치에 놓아야 한다. API 참조 (http://docs.angularjs.org/api/ngMock.$httpBackend)

// 로그인 서비스 테스트에서 계속 에러가 발생하여 Re-Thinking 중이다. 

// 에러 코드 

'use strict';


describe('Service: msRestfulApi', function () {


  // load the service's module

  beforeEach(module('meanstackApp'));


  // instantiate service

  var msRestfulApi, httpBackend, scope, resource;

  beforeEach(inject(function ($injector) {

    msRestfulApi = $injector.get('msRestfulApi');

    httpBackend = $injector.get('$httpBackend');

    scope = $injector.get('$rootScope');

    resource = $injector.get('$resource');


    httpBackend.when('POST', '/api/v1/user/login').respond(200, {'id': 1});

    

  }));


  it('should call test', function() {

    var request = {

      'area' : 'user',

      'resource' : 'login',

      'id' : '',

      'request' : {}

    };


    msRestfulApi.login(request, function(response){

      expect(response.id).toBe(1);

      console.log(response);

    }, function(error){

      console.log(error);

    });


    httpBackend.flush();

    expect(r.id).toBe(1);


  });


});

  


<참조>

  - Jasmine 문법

  - Jasmine 소개

  - Karma 테스트 하기

  - Protractor Seed 소개 : Protractor WebDriver 업데이트 

  - WebDriverJS 소개 

posted by Peter Note
2014. 5. 28. 21:20 AngularJS/Start MEAN Stack

다국어 처리를 Angular.js 기반인 클라이언트단에서 처리하는 방법에 대해 알아본다. angular-translateangular-gettext 두가지가 존재하는데 여기서는 angular-gettext를 살펴보도록 한다 



준비사항

  - 먼저 poedit을 설치한다. 언어 번역을 일괄적으로 관리할 수 있는 툴이다. 기본 영문에서 한글 번역내역을 입력하여 언어별로 .po 파일을 관리한다 

  - angular-gettext 모듈을 사용한다. --save 옵션넣어서 bower.json 업데이트 한다. 인스톨 가이드를 참조한다 

$ bower install angular-gettext --save

  - grunt-angular-gettext 를 설치한다. --save-dev 옵션. 해당 모듈은 .po파일을 생성하거나 생성된 내용을 angular 자바스크립트로 변환을 담당한다 

$ npm install grunt-angular-gettext --save-dev



Grunt Config 수정

  - grunt-angular-gettext를 통하여 poedit에서 읽을 수 있는 원본 파일(.pot)을 생성하고, 원본에서 다른 언어의 번역을 넣은 파일(.po)을 읽어서 angular 형식의 자바스크립트 파일을 생성한다. 

  - Gruntfile.js 첨부내역

    + nggettext_extract : 다국어 지원이 필요한 .html을 읽어서 원본 origin_template.pot 파일 생성 위치와 파일명 지정

    + nggettext_compile : 다국어 .po 파일을 읽어서 angular 형식의 파일을 만들 위치와 파일명 지정. 

                                   별도의 모듈로 gettext_translation을 적용한다 

    // Translation for multi-lang

    nggettext_extract: {

      pot: {

        files: {

          'app/translation/po/origin_template.pot': [

            'app/index.html',

            'app/views/**/*.html',

            'app/domain/**/*.html'

          ]

        }

      },

    },

    nggettext_compile: {

      all: {

        options: {

          module: 'gettext_translation'

        },

        files: {

          'app/translation/translation.js': ['app/translation/po/*.po']

        }

      },

    }, 



다국어 지원 자바스크립트 생성 작업

  - html 수정 : 다국어 지원이 필요한 부분에 translate 애트리뷰트를 넣는다. 한줄은 <span></span> 태그를 사용한다

    <form name="signupForm" novalidate>

        <div class="session-signup-main-subject">

            <span translate>Create an Account</span>

        </div>

        <div class="session-signup-name-subject">

            <span translate>Name</span> 

            <span style="color:red" ng-show="focusName" translate> @Please input your full name.</span>

        </div>

        <input type="text" name="user_name" placeholder="{{'Enter Full Name'|translate}}" class="session-signup-name-input" ng-model="user.name" required sg-focus="focusName">

        <div class="session-signup-email-subject">

            <span translate>Email</span> 

            <span style="color:red" ng-show="focusEmail" translate> @Please input your email.</span>

        </div>

        <input type="email" name="user_email" placeholder="{{'Enter Email'|translate}}" class="session-signup-email-input" ng-model="user.email" required sg-focus="focusEmail">

        <div class="session-signup-password-subject">

            <span translate>Password</span> 

            <span style="color:red" ng-show="focusPassword" translate> @Please input your password.</span>

        </div>

        <input type="password" name="user_password" placeholder="{{'Enter Password'|translate}}" class="session-signup-password-input" ng-model="user.password" required sg-focus="focusPassword">

        <div class="session-signup-have">

            <span translate>Have an account?</span>

        </div>

        <a ui-sref="signin" class="session-signup-login-btn">

            <span translate>Log-in</span>

        </a>

        <button class="session-signup-create-btn" ng-click="signup(signupForm)">

            <div class="session-signup-create-btn-text">

                <span translate>Create New Account</span>

            </div>

        </button>

    <form> 


  - grunt를 통해서 html의 translate 을 해석하여 origin_template.pot파일을 생성한다

$ grunt nggettext_extract


  - origin_template.pot 파일을 poedit로 import 한다. 한글을 언어로 설정하여 번역을 한후, ko_KR.po 파일을 생성한다.

    + 새 번역 만들기 클릭 후 origin_template.pot파일 선택한 후 번역 언어 선택함  

    

    + 번역을 입력하고 "다른 이름으로 저장하기..."를 선택하여 ko_KR.po 파일을 만든다 

    


  - 번역된 ko_KR.po 파일 내역을 translate.js 파일로 만들기 

$ grunt nggettext_compile 


// ko_KR.po 파일 내역

msgid ""

msgstr ""

"Project-Id-Version: \n"

"POT-Creation-Date: \n"

"PO-Revision-Date: 2014-05-27 16:20+0900\n"

"Last-Translator: \n"

"Language-Team: \n"

"Language: ko_KR\n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=UTF-8\n"

"Content-Transfer-Encoding: 8bit\n"

"X-Generator: Poedit 1.6.5\n"

"X-Poedit-Basepath: .\n"

"Plural-Forms: nplurals=1; plural=0;\n"


#: app/domain/session/signup/signup.html

msgid "@Please input your email."

msgstr "@이메일주소를 입력해 주세요."


#: app/domain/session/signup/signup.html

msgid "@Please input your full name."

msgstr "@전체 이름을 입력해 주세요."


#: app/domain/session/signup/signup.html

msgid "@Please input your password."

msgstr "@전체 패스워드를 입력해 주세요."


#: app/domain/session/signup/signup.html

msgid "Create New Account"

msgstr "신규 계정 생성"


#: app/domain/session/signup/signup.html

msgid "Create an Account"

msgstr "계정 생성"


#: app/domain/session/signup/signup.html

msgid "Email"

msgstr "이메일"


#: app/domain/session/signup/signup.html

msgid "Enter Email"

msgstr "이메일 입력"


#: app/domain/session/signup/signup.html

msgid "Enter Full Name"

msgstr "이름 입력"


#: app/domain/session/signup/signup.html

msgid "Enter Password"

msgstr "패스워드 입력"


#: app/domain/session/signup/signup.html

msgid "Have an account?"

msgstr "계정이 있나요?"


#: app/domain/session/signup/signup.html

msgid "Log-in"

msgstr "로그인"


#: app/domain/session/signup/signup.html

msgid "Name"

msgstr "이름"


#: app/domain/session/signup/signup.html

msgid "Password"

msgstr "패스워드"



// translate.js 파일로 ko_KR.po 파일을 기반으로 생성된 내역 

angular.module('gettext_translation').run(['gettextCatalog', function (gettextCatalog) {

/* jshint -W100 */

    gettextCatalog.setStrings('ko_KR', {"@Please input your email.":"@이메일주소를 입력해 주세요.","@Please input your full name.":"@전체 이름을 입력해 주세요.","@Please input your password.":"@전체 패스워드를 입력해 주세요.","Create New Account":"신규 계정 생성","Create an Account":"계정 생성","Email":"이메일","Enter Email":"이메일 입력","Enter Full Name":"이름 입력","Enter Password":"패스워드 입력","Have an account?":"계정이 있나요?","Log-in":"로그인","Name":"이름","Password":"패스워드"});

/* jshint +W100 */

}]);


  - 별도의 모듈 gettext_translation을 생성하고 index.html에 파일들을 추가한다  

// translationModule.js 을 생성

angular.module('gettext_translation', []); 


// index.html에 <script>도 추가한다 

<script src="bower_components/angular-gettext/dist/angular-gettext.js"></script>

<script src="translation/translationModule.js"></script>

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


  - 메인 애플리케이션인 App.js 에 gettext와 gettext_translation모듈을 추가하고, run 메소드에 언어 설정을 한다

var App = angular.module('studyGpsApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ui.router',

  'ui.bootstrap',

  'restangular',

  'gettext',

  'gettext_translation'

]);


App.run(function ($rootScope, gettextCatalog) {

    //translate for multi-lang

    $rootScope.setLang = function(lang, isDebug) {

      if(lang) {

        gettextCatalog.currentLanguage = lang;

      } else {

        gettextCatalog.currentLanguage = 'ko_KR';

      }

      gettextCatalog.debug = isDebug;

    }

    // init

    $rootScope.setLang('ko_KR', true);


  - 결과화면

  



지속적인 번역 파일 만들기 

  - 만일 화면이 추가되어 신규 번역이 필요할 경우 다음과 같이 수행을 하면 추가된 부분만 따로 설정 할 수 있다. 

  - poedit을 사용하게 되면 html 마다 반복적으로 사용되는 용어의 중복을 unique하게 관리 할 수 있고, 별도의 .js 코딩을 할 필요가 없다.

  - 많은 언어를 관리해야 한다면 poedit을 통해 관리 편의성을 얻을 수 있다

// 먼저 origin_template.pot 파일을 생성한다 

$ grunt nggettext_extract


// poedit 에서 ko_KR.po 파일을 open 한 후에 다음의 메뉴를 선택하고 origin_template.pot를 선택하면 새롭게 추가된 번역할 내역이 자동으로 ko_KR.po 파일에 추가되어 나온다 

// 이제 <언어별>.po 파일들을 translate.js 파일로 만든다. 

$ grunt nggetext_compile 


* angular-translate 모듈이 있는데 해당 모듈은 html 화면에 기본 랭귀지가 아닌 KEY값을 넣어서 바인딩해야 하는 번거로움이 존재하고, poedit와 같은 도구를 통한 번역의 편리성을 제공하지 않는다. 



Gulp를 사용할 때

  - gulp용의 angular-gettext를 설치한다. 물론 gulp도 설치한다. 

$ npm install gulp 

$ npm install gulp-angular-gettext

  

  - gulpfile.js 의 설정 내역

    + gulp-rename 플러그인을 이용해서 파일명을 바꾼다. 

    + translation 할때는 format을 javascript로 한다. 포맷은 json과 javascript가 있다.

var rename = require('gulp-rename');

var gettext = require('gulp-angular-gettext');


gulp.task('lang', ['extract', 'translation']);

gulp.task('extract', function() {

  return gulp.src(['./www/app/**/*.html'])

             .pipe(gettext.extract('origin_template.pot', {}))

             .pipe(gulp.dest('./www/lib/common/translation/po/'));

});


gulp.task('translation', function() {

  return gulp.src('./www/lib/common/translation/po/*.po')

             .pipe(gettext.compile({

                module: 'mb.translation',

                format: 'javascript'

             }))

             .pipe(rename('lib/common/translation/translation.js'))

             .pipe(gulp.dest('./www'));

});




<참조>

  - AngularJS Multi-Language Support

  - AngularJS GetText Homepage

  - Grunt Angular GetText GitHub

  - angular-translate Hompage


posted by Peter Note
2014. 2. 4. 10:43 AngularJS/Start MEAN Stack

메뉴의 우측에 로그인을 생성하고 로그인 화면을 트위터 부트스트랩을 이용하여 만들고 로그인 처리를 한다. 


사전 준비 

  트위터 부트스트랩을 이용하여 먼저 화면 프로토 타입을 만든다. 그리고 앵귤러 Controller를 코딩한 후 서버와 통신을 위한 앵귤러 Service를 만들자. 그리고 필요에 따라 Framework 요소로 필요한 것을 만들어 간다. 하나의 업무처리를 위하여 HTML+Controller+Service 를 기억억한다.  


  - 비즈니스 HTML View 작성

  - 비즈니스 Controller 코딩

  - 비즈니스 Service 코딩



1. 로그인 메뉴 추가하기

  메뉴는 menu.html 로 별도 구성하였고, 맨 하단에 로그인이 안되었을 때와 로그인이 되었을 때를 나타내는 <li> 태그를 넣어준다. currentUser는 로그인 성공후 $rootScope에 저장되는 사용자 객체이다. <li> 태그는 ng-hide/ng-show에의해 보여졌다 안보여졌다 한다.

     ... 중략 ...

          <li class="divider"></li>

          <li class="dropdown-header">Members</li>

          <li data-ng-class="{ active: $state.includes('gurumember') }">

            <a ui-sref="gurumember"><i class="fa fa-users "></i> 멤버소개</a>

          </li>

        </ul>

      </li>

    </ul>


    <ul class="nav navbar-nav navbar-right">

      <li ng-hide="false">

        <a href="#" ui-sref="login">Login</a>

      </li>

      <li ng-show="false">

        <a href="#" ng-click="logout()">

          [{{ currentUser.name }}] Logout

        </a>

      </li>

    </ul>


  </div>

</div>



2. 로그인 화면 구성

  ng-hide="false" 이므로 현 상태에서는 "Login" 메뉴가 나오고, 메뉴 클릭시 ui-sref="login" 에 따라 views/login.html 화면으로 이동을 한다.  Bootsnipp에서 login 을  (http://bootsnipp.com/search?q=login) 검색하면 다양한 로그인 창 디자인이 나오므로 활용해 본다.


  - 로그인을 할 때 컨트롤러에서 처리할 메서드를 정의한다 

    submitLogin()

  - 정보 객체를 정의한다  

    login = { email: 'xxx', password: 'xxx' }

  - type 과 required 지정을 통해 필수항목 유효성 체크를 한다 

<div class="row">

  <div class="col-md-4 col-md-offset-4">

    <h3 class="text-center">LOGIN</h3>

    <form name="loginForm" ng-submit="submitLogin()">

      <div class="form-group">

        <div class="input-group">

          <span class="input-group-addon">

            <i class="fa fa-envelope-o"></i>

          </span>

          <input id="login_email" type="email" name="email" class="form-control input-lg" placeholder="Email" ng-model="login.email" required>

        </div>

      </div>


      <div class="form-group">

        <div class="input-group">

          <span class="input-group-addon">

            <i class="fa fa-asterisk"></i>

          </span>

          <input id="login_password" type="password" name="password" class="form-control input-lg" placeholder="Password" ng-model="login.password" required>

        </div>

      </div>


      <button type="submit" class="btn btn-primary btn-lg btn-block btn-shadow">Login</button>

    </form>

  </div>

</div>


  우측 상단의 "Login" 메뉴 클릭시 출력되는 최종화면이다 



3. 로그인 앵귤러 Controller 개발 

  로그인 화면에 대한 처리를 하는 컨트롤러는 LoginCtrl (app/scripts/controllers/login.js) 이다. 앵귤러의 컨트롤러는 뷰와 모델 객체를 주고 받으며 처리하는 역할만을 담당하도록 만든다. 즉, 서버와 연결하고 처리하는 것은 앵귤러 서비스를 만들고 주입(DI)을 받아 사용한다. 로그인이 성공했다고 가정하여 UI-Router의 $state를 이용하여 main 화면으로 이동하는 코드를 넣었다. 

 'use strict';


angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  function ($scope, $state) {

   $scope.submitLogin = function() {

    console.log($scope.login);

     $state.go("main");

   }

  }]);



로그인 앵귤러 Service 개발 

  로그인 정보를 받아서 서버와 통신하는 모듈을 만들도록 한다. Node.js와 RESTful 방식으로 요청을 처리한다. 앵귤러에서는 HTTP 요청 처리를 위하여 $http와 이를 보다 추상화한 $resource를 제공한다. 하지만 클라이언트와 서버사이의 통신을 위하여 몇가지 공통모듈을 만들 필요가 있다. 


  - 서버와 주고받는 요청/응답을 추상화한 객체 

  - $resource를 추상화한 서비스   



1. 서버 응답/요청 객체 추상화 

  자바의 J2EE 스펙중 Servlet 스펙을 보면 요청/응답을 처리하는 서블릿은 HTTPServletResponse/HTTPServletRequest가 있다. 이와 유사하게 앵귤러와 Node.js에 사이에 주고 받는 JSON 객체를 추상화 한다. MS를 MEANStack의 약어로 공통모듈의 경우 앞에 붙여 사용한다. 요청객체는 앵귤러 Factory 로 개발한다.  


  - 요청객체 : MSRequest 를 통해 정형화된 형태의 JSON 객체를 생성한다 

  - 응답객체 : MSResponse 는 서버로 부터 전달되는 정형화된 형태의 JSON 객체이다 


  RESTful API를 만들기 위한 몇가지 원칙을 지키고 그외에는 변형하여 사용토록 한다. URI 정의시 몇가지 원칙만을 가지고 진행토록한다. 복잡하게 하지 말고 Stateless하면서 Resource 접근에 대해서 명사를 사용하며 동사적 의미는 HTTP POST, GET, PUT, DELETE를 사용하며, Collection의 경우는 복수를 사용한다. 즉 더 복잡한 부분이 나오면 그때 가서 확장하거나 별도 구성하여 사용하고 몇가지 원칙을 정해서 사용토록 한다 


  - prefix 로 버전을 반드시 붙인다 : api/v1

  - 3단계의 정의를 한다 : /:area/:resource/:id 

    + area : authentication, person, tech, guru 등의 resource 영역 구분

    + resource : login, mylevel(myfollowing), angularjs(nodejs, expressjs, mongodb), month(member)같은 좀 더 구체적인 구분

    + id : GET, PUT, DELETE의 경우 옵션적으로 사용

    + 그외의 데이터는 Key=Value 파라미터 값으로 넘긴다 


// MSRequest 생성 및 개발 

$ yo angular:factory ms-factory

   create app/scripts/services/ms-factory.js

   create test/spec/services/ms-factory.js


// ms-factory.js

angular.module('meanstackApp')

  .factory('msRequestFactory', function () {

  

  var createRequest = function(area, resource, id, request) {

    if (!request)

      request = {};


    return {

      "area" : area,

      "resource" : resource,

      "id" : id,

      "request" : request

    };

  };


  return {

    createRequest : createRequest

  };


});


// 공통 호출 팩토리 생성

$ yo angular:factory ms-restful-api

   create app/scripts/services/ms-restful-api.js

   create test/spec/services/ms-restful-api.js


// ms-restful-api.js 

angular.module('meanstackApp')

  .factory('msRestfulApi', ['$resource', function ($resource) {

  var prefixUrl = '/api/v1';


  return $resource(

      prefixUrl + '/:area/:resource/:id', 

     {

       area : "@area",

       resource : "@resource",

       id : "@id"

     },

     {

      'get':    {method:'GET', isArray:true},

      'save':   {method:'POST'},

      'update': {method:'PUT'},

      'delete': {method:'DELETE'},

      'login':  {method:'POST'}

    });

  }]);


  $resource의 파라미터는 첫번째 URI 두번째 URI의 구분자에 맵핑되는 JSON, 세번째는 사용자 정의 메소드이다. 



2. 로그인 Service 개발 

  컨트롤에서 로그인 관련 서버호출을 위하여 MSRequest 팩토리와 RESTful하게 호출할 수 있는 팩토리를 만들었다. 이를 활용하는 앵귤러 Service 코딩해 보자. 


  - 사용자 로그인을 처리하는 서비스를 만든다.

  - 서비스는 View에서 Controller를 통하여 로그인 정보(params)를 받아서 Service로 넘긴다.

  - 성공적으로 완료되면 Controller에서 넘겨준 펑션(successCallback)을 수행한다. 


Service는 순수하게 서버와의 요청 처리만을 담당하고 Controller는 HTML View에서 넘어온 Model 값의 핸들링과 이후 Service에서 성공을 하면 실행할 successCallback을 갖는다. successCallback은 UI Routing 또는 View의 변경을 수행한다. Service와 Controller 서로의 역할을 정확히 나눔으로써 유지보수성을 높이도록 하였다. 


// 로그인 관련 서비스를 만든다 

$ yo angular:service session-service

   create app/scripts/services/session-service.js

   create test/spec/services/session-service.js


// session-service.js

angular.module('meanstackApp')

  .service('SessionService', [

  'msRequestFactory', 

  'msRestfulApi', 

  '$log',

  function SessionService(msRequestFactory, msRestfulApi, $log) {

    

    this.login = function(paramssuccessCallback) {

    // 1) create request

    var request = msRequestFactory.createRequest('authentication', 'login', '');

   

    // 2) set params

    request.params = {

    email: params.email,

    password: params.password

    }


    // 3) call ajax 

    msRestfulApi.login(request, 

    function(response) {

    // success

    successCallback(response);

    }, 

    function(error){

    // fail

    $log.error('Server Exception is ', error);

    })

  };


  }]);


  사용자가 로그인을 한후 사용자 정보를 담는 SessionInfo 객체를 관리하는 서비스를 만들어 보자. 

  - 사용자 정보 저장소로 HTML5의 localStorage를 우선 사용한다 

  - 현재 사용자 정보를 $rootScope.currentUser에 담아서 애플리케이션 전체 영역에서 사용토록 한다 

// 세션 정보 생성 

$ yo angular:service session-info

   create app/scripts/services/session-info.js

   create test/spec/services/session-info.js


// session-info.js

angular.module('meanstackApp')

  .service('SessionInfo', ['$rootScope', function SessionInfo($rootScope) {

    this.localStorageKey = "__SESSION_INFO";

    try {

      $rootScope.currentUser = JSON.parse(localStorage.getItem(this.localStorageKey) || "{}");

    } catch(e) {

      $rootScope.currentUser = {};

    }


    this.getCurrentUser = function() {

      return $rootScope.currentUser;

    }


    this.isUserSignedIn = function() {

      if(this.getCurrentUser() && this.getCurrentUser().id) {

        return true;

      } else {

        return false;

      }

    };


    this.setUserInfo = function(info) {

      angular.extend($rootScope.currentUser, info);

      localStorage.setItem(this.localStorageKey, JSON.stringify($rootScope.currentUser));

    };


    this.reset = function() {

      $rootScope.currentUser = {};

      localStorage.setItem(this.localStorageKey, JSON.stringify($rootScope.currentUser));

    };

  }]);



  SessionService에서 로그인 요청이 성공하면 SessionInfo를 호출하는 코드를 넣도록 한다. 

// SessionInfo를 사용하는 SessionService 수정 

angular.module('meanstackApp')

  .service('SessionService', [

   'msRequestFactory', 

   'msRestfulApi', 

   'SessionInfo',

   '$log',

   function SessionService(msRequestFactory, msRestfulApi, SessionInfo, $log) {

    

    this.login = function(params, successCallback) {

     // 1) create request

     var request = msRequestFactory.createRequest('authentication', 'login', '');

    

     // 2) set params

     request.params = {

     email: params.email,

     password: params.password

     }


     // 3) call ajax 

     msRestfulApi.login(request, 

     function(response) {

       // success

        SessionInfo.reset();

                   SessionInfo.setUserInfo(response);

        successCallback(response);

     }, 

     function(error){

       // fail

                  alert('Login Failed');

       $log.error('Server Exception is ', error);

     })

   };


  }]);



3. 로그인 Controller 개발

  이제 앵귤러 Service가 만들어 졌으니 Service를 사용하는 Controller를 코딩해 보자. 로그인 Service 가 성공하면 메인화면으로 이동한다.  

// login.js 의 기존 코드 

angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  function ($scope, $state) {

   $scope.submitLogin = function() {

    console.log($scope.login);

     $state.go("main");

   }

  }]);


// SessionService를 사용하는 수정 코드 

angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  'SessionService',

  'SessionInfo',

  '$log',

  function ($scope, $state, SessionService, SessionInfo, $log) {


   $scope.submitLogin = function() {

    SessionService.login($scope.login, changeState);

   }


   function changeState(response){

    alert(SessionInfo.getCurrentUser().name + '님 반갑습니다 :)');

        $state.go("main");

   }

  }]);


최종적으로 Chrome 개발자 도구의 console에 아무런 에러가 없으면 모든 서비스와 컨트롤러가 앵귤러 초기화가 잘 되었다는 신호이다. 로그인을 해보면 아마도 404에러가 날 것이다. 아직 서버가 준비되지 않았기 때문인데, 이는 다음장에서 테스트 코드를 작성해서 확인해 보록한다. 다음과 같인 나오면 성공이다. 



4. 로그인 html 수정

  SessionInfo에는 사용자가 로그인 상태인지 체크하는 메소드가 존재한다. 이를 menu.html 에 적용하자 

<ul class="nav navbar-nav navbar-right">

<li ng-hide="SessionInfo.isUserSignedIn()">

<a href="#" ui-sref="login">Login</a>

</li>

<li ng-show="SessionInfo.isUserSignedIn()">

<a href="#" ng-click="logout()">

[{{ currentUser.name }}] Logout

</a>

</li>

</ul>


위의 코드는 해당 브랜치에 존재한다. 

https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/angular_step03_login-ctrl-service

posted by Peter Note
2014. 1. 30. 11:15 AngularJS/Start MEAN Stack

UI-Router를 이용한 화면 라우팅 설정을 app/scripts/app.js 메인 애플리케이션 파일에 설정하였다. 설정 내용중 각 라우팅되는 HTML View의 제어를 담당하는 Controller를 yeoman을 사용해서 생성해 보자 



HTML과 Controller 

  앵귤러는 MVW 프레임워크라고 하는데 여기서 M은 model 이고 V는 View, W은 Whatever를 의미하여 무엇이든지 올 수 있다는 의미이다. Whatever에는 싱글톤패턴의 서비스, 팩토리 또는 필터, 디렉티브(Directive)등이 올 수 있다. 


  - Model : Controller의 $scope를 통한 자동 two-way binding 을 한다 

  - View : HTML에 $scope의 모델들을 설정하거나 앵귤러의 디렉티브를 이용하여 직관적인 설정한다  


  앵귤러의 데이터 모델은 바로 Scope에 의해서 연결이 되며, app.js 가 최초 수행 되면 $rootScope가 생성되고 그 하위로 $scope가 생성된다. 이들은 계층적으로 연결되어 상위의 scope 데이터에 접근할 수 있다. 만일 Chrome 브라우져를 사용한다면 scope 객체들의 계층구조를 보기 위하여 별도의 크롬도구를 설치해야 한다. 


 1) AngularJS Batarang: https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk 설치 

 2) 크롬 개발자도구 : 윈도우(Ctrl+Shift+i), 맥(Command+Option+i) 연다 

 3) 맨 우측의 AngularJS를 선택하고 Enable 옵션을 체크한다 

 4) < Scope (003) 에서 003은 앵귤러가 Scope를 생성하고 부여한 아이디이고 클릭하면 우측에 Scope에 저장된 정보를 볼 수 있다.



1. 화면별 컨트롤러 생성하기 

  Yeoman에서 앵귤러 프레임워크의 기능을 자동 생성해 주는 것은 yo 명령이다. 우리는 meanstack 애플케이션 생성을 위하여 "generator-angular"를 설치하였는데 yo 뒤의 명령은 관례에 따라 "yo angular:<기능> <명칭>"으로 사용할 수 있다. 


  - yo의 generator가 만일 generator-mean 이고 geneartor-mean안에 <기능>으로 controller가 있다면 

  - yo mean:controller sample 을 수행하면 보통은 컨트롤러 코드와 컨트롤러 테스트 코드가 자동 생성된다

  -즉 "yo <generator- 두의 명칭>:<genertor가 제공하는 기능명칭>  <사용자정의 명칭>" 으로 명령은 구성된다 

  - <사용자정의 명칭> 에서 이름을 지정할 때는 "user-biz" 라고 명칭을 주면 index.html에는 "user-biz.js" 파일이 추가 되고, 기능의 명칭은 "UserBiz<기능약어>" 식으로 명칭이 만들어 진다.


  generator-angular 기능 목록은 홈페이지를 참조한다 (https://github.com/yeoman/generator-angular) 컨트롤러 생성시 테스트 코드 파일이 자동으로 생성된다 

// yo angular:controller <명칭> 으로 각 메뉴의 컨트롤러를 생성한다.

$ yo angular:controller my-level

   create app/scripts/controllers/my-level.js

   create test/spec/controllers/my-level.js


$ yo angular:controller my-qna

   create app/scripts/controllers/my-qna.js

   create test/spec/controllers/my-qna.js


$ yo angular:controller my-following

   create app/scripts/controllers/my-following.js

   create test/spec/controllers/my-following.js


$ yo angular:controller tech-area

   create app/scripts/controllers/tech-area.js

   create test/spec/controllers/tech-area.js


$ yo angular:controller ranking

   create app/scripts/controllers/ranking.js

   create test/spec/controllers/ranking.js


$ yo angular:controller member

   create app/scripts/controllers/member.js

   create test/spec/controllers/member.js


$ yo angular:controller login

   create app/scripts/controllers/login.js

   create test/spec/controllers/login.js


// index.html에 자동으로 추가된 것을 볼 수 있다.

<!-- build:js({.tmp,app}) scripts/scripts.js -->

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

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

<script src="scripts/controllers/my-level.js"></script>

<script src="scripts/controllers/my-qna.js"></script>

<script src="scripts/controllers/my-following.js"></script>

<script src="scripts/controllers/tech-area.js"></script>

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

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

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

<!-- endbuild -->


  컨트롤러를 생성했는데 - 또는 . 를 사용하면 앞뒤의 문자들의 첫글자가 대문자로 변형되어 설정이 되고 뒤에 "Ctrl" 이 자동으로 붙는다 

// app/scripts/controllers/my-level.js 소스

'use strict';


angular.module('meanstackApp')

  .controller('MyLevelCtrl', function ($scope) {

    $scope.awesomeThings = [

      'HTML5 Boilerplate',

      'AngularJS',

      'Karma'

    ];

  });

  

  이제 app/scripts/app.js에 컨트롤러를 설정해 보자 

$stateProvider

      .state('main', {

        url: '/',

        templateUrl: 'views/main.html',

        controller: 'MainCtrl'

      })

      .state('login', {

        url: '/login',

        templateUrl: 'views/login.html',

        controller: 'LoginCtrl'

      })

      .state('mylevel', {

        url: '/mylevel',

        templateUrl: 'views/mylevel.html',

        controller: 'MyLevelCtrl'

      })

      .state('myqna', {

        url: '/myqna',

        templateUrl: 'views/myqna.html',

        controller: 'MyQnaCtrl'

      })

      .state('myfollowing', {

        url: '/myfollowing/{userId:[0-9]{1,4}}',

        templateUrl: 'views/myfollowing.html',

        controller: 'MyFollowingCtrl'

      })

      .state('techarea', {

        url: '/techarea/{techId:[0-9]{1,4}}',

        templateUrl: 'views/techarea.html',

        controller: 'TechAreaCtrl'

      })

      .state('gururanking', {

        url: '/gururanking',

        templateUrl: 'views/gururanking.html',

        controller: 'RankingCtrl'

      })

      .state('gurumember', {

        url: '/gurumember',

        templateUrl: 'views/gurumember.html',

        controller: 'MemberCtrl'

      });


  my-following이나 tech-area를 보면 아이디를 설정하였고 myfollowing.html에서 $stateParam을 사용하기위해 meanstackApp 모듈의 run() 에서 $rootScope에 $stateParams를 저장한다. 그리고 페이지가 전환 되기 시작할 때와 전환하여 로딩 완료되었을 때의 이벤트를 처리하기 위한 Listener를 등록한다. 

// myfollowing.html 

<div class="row">

  <div class="col-md-12">

    <h1>myfollowing.html</h1>

     User ID: {{$stateParams.userId}}

  </div>

</div>


// app/scripts/app.js

.run(['$rootScope', '$state', '$stateParams', 

  function ($rootScope, $state, $stateParams) {

    $rootScope.$state = $state;

    $rootScope.$stateParams = $stateParams;

  }]);


  - html에서 $stateParams 를 찾기위하여 자신의 $scope.$stateParams를 찾는다 없으면 다시 상위의 $rootScope.$stateParams을 찾게 된다. 따라서 클라이언트 애플리케이션 전역에서 사용할 내용들이 있다면 $rootScope에 저장한다. 

  - myfollowing 메뉴를 선택하면 Scope (009) 의 아이디값이 변경된다. 메뉴를 누를 때마다 Scope의 아이디가 변경되는데 이는 클라이언트 화면이 변경될 때 이전 Scope는 destroy되고 새로운 Scope가 생성되기 때문이다. 즉, 앵귤러가 화면 라우팅이되고 해당 화면의 컨트롤러에 신규 $scope 을 생성하여 DI (Dependency Injection)해준다 



2. 컨트롤러 코드 변경하기 

  애플리케이션 배포시에는 "grunt build"를 수행하는데 이때 .js에 대한 단축화(minification)이 수행되어 변수명을 알 수 없는 코드로 바꾸어준다. 앵귤러에서는 $로 시작하는 명칭이 변경이 되면 안되기 때문에 yo로 생성된 코드를 그대로 사용할 수 없다.  '$scope' 문자를 지정해 주면 function($scope) {} 가 최소화 되어 function($ab){} 로 바뀌어도 DI시에 '$scope'로 지정된 $scope 객체가 주입된다.

 angular.module('meanstackApp')

 .controller('MyFollowingCtrl', [

  '$scope',

  function ($scope) {

    ... 중략 ...

  }]);

  

  다른 컨트롤러도 해당 방식으로 전부 수정한다. 소스는 anuglar_setp02_insert-ctrl-menu 브랜치를 체크아웃한다 

https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/anuglar_setp02_insert-ctrl-menu

posted by Peter Note
2014. 1. 29. 23:56 AngularJS/Start MEAN Stack

CSS Framework과 클라이언트 UI Routing을 위한 라이브러리 설치가 끝났으니 이제 본격적으로 메인화면을 만들어 보도록 하죠. 메인 화면의 디자인은 CSR에서 내용을 참조하여 Twitter Bootstrap의 반응형 웹 디자인 메뉴로 구성한다. 



전체 구조 이해하기 

  우선 메뉴를 만들기 전에 index.html의 전체 구조를 이해하자. SPA 방식의 싱글페이지 애플리케이션 개발이란 index.html의 일부 DOM을 변경함으로써 화면을 전화하여 보여주는 것이다. 초기 index.html 레이아웃은 단순화 하여 사용토록 한다. 


  - 메뉴 html을 별도 파일로 분리 

  - 메뉴 html에 링크 설정하기 

  - 메뉴에 링크된 화면의 html 파일 생성 


// index.html 기존 설정

<body ng-app="meanstackApp">


    <!-- Add your site or application content here -->

    <div class="container" ng-view=""></div>


</body>



1. 메뉴관련 html 파일 분리

  우선 메뉴관련 파일을 분리해서 시작한다. app/views/menu.html 파일을 생성하고 index.html 파일에 include 한다.

<!-- Add your site or application content here -->

<!-- menu -->

<div ng-include="' /views/menu.html '"></div>

  menu.html 안에 이제 부트스트랩의 반응형 웹 디자인 메뉴를 만들어 보자. 반응형 웹 디자인 메뉴란 해상도에 따라 메뉴의 형태가 자동으로 변하는 것을 말한다. 주로 데스크톱과 모바일기기의 해상도에 맞게 메뉴 형태가 변한다. 처음에 부트스트랩을 직접 수작업을 해보는 것이 도움이 되지만 여기서는 부트스트랩용 저작도구인  Bootply(http://bootply.com/)를 사용해서 만들어 본다.  


  - Bootply의 "Drag-and-Drop" 가운데 메뉴를 선택한다

  - Bootstrap 3.0 에서 "Basic starter"를 선택한다 

  - html에서 메뉴에 대한 부분만 취한다 



 menu.html 에서 <div class="navbar navbar-inverse navbar-fixed-top"> 내용은 index.html의 ng-include가 있는 <div>에 class설정한다. 

// index.html

<div class="navbar navbar-inverse navbar-fixed-top" ng-include="' /views/menu.html '"></div>


// menu.html 

  <div class="container">

    <div class="navbar-header">

      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">

        <span class="icon-bar"></span>

        <span class="icon-bar"></span>

        <span class="icon-bar"></span>

      </button>

      <a class="navbar-brand" href="#">Brand</a>

    </div>

    <div class="collapse navbar-collapse">

      <ul class="nav navbar-nav">

        <li class="active"><a href="#">Home</a></li>

        <li><a href="#about">About</a></li>

        <li><a href="#contact">Contact</a></li>

      </ul>

    </div><!--/.nav-collapse -->

  </div>

  

  "grunt serve"로 수행을 해보면 입력한 메뉴 구조를 볼 수 있다. 화면의 사이즈를 줄이면 메뉴형태가 변화는 것 또한 볼 수 있다. 

  - 데스크톱 해상도

  - 모바일 해상도


2. 메뉴의 링크 설정하기 

  menu.html 파일안에 링크에 대해 설정한다. 요구사항 정의를 보면 MEANStack을 위한 Q&A 서비스를 만드는 것이다. 하기와 같은 대분류 메뉴와 소분류 메뉴를 링크설정해 본다 

  

  - 나의 Q&A : 나의 질문 답변, 내가 팔로잉한 사람

  - Tech 글 : MEAN Stack 관련 메뉴

  - 명예의 전당 : 이번달 명예의 전당(인기글), 멤버소개 


부트스트랩은 기본적인 사항은 홈페이지를 통해 몇시간 정도 공부하거나 오픈 튜토리얼 사이트에서 익히고 시작하는 것이 좋다. 그리고 응용에 관련된 부분은 Bootsnipp (http://bootsnipp.com/) 에서 유용한 형태를 취하여 사용해 본다. 여기서는 부트스트랩의 기본적인 Navigation bar 구조를 참조한다.

참조: http://getbootstrap.com/components/#navbar


// menu.html 변경 내용

<div class="container">

  <div class="navbar-header">

    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">

      <span class="icon-bar"></span>

      <span class="icon-bar"></span>

      <span class="icon-bar"></span>

    </button>

    <a class="navbar-brand" href="#"> MEANStack.net</a>

  </div>

  <div class="collapse navbar-collapse">

    <ul class="nav navbar-nav">

      

      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown"> My Area <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Status</li>

          <li>

            <a href="#"> My Level: 평민</a> 

          </li>

          <li>

            <a href="#"> 질문: 3, 답변: 1</a>

          </li>

          <li class="divider"></li>

          <li class="dropdown-header">Following</li>

          <li>

            <a href="#"> 윤영식</a>

          </li>

        </ul>

      </li>


      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown"> Tech Area <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">SPA</li>

          <li>

            <a href="#"> Angular.js</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">Middleware</li>

          <li>

            <a href="#"> Node.js</a>

          </li>

          <li>

            <a href="#"> Express.js</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">NoSQL</li>

          <li>

            <a href="#"> MongoDB</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">Eco Tools</li>

          <li>

            <a href="#"> Yeoman</a>

          </li>

        </ul>

      </li>


      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown"> GuruGuru <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Ranking</li>

          <li>

            <a href="#"> 명예의 전당</a>

          </li>

          <li class="divider"></li>

          <li class="dropdown-header">Members</li>

          <li>

            <a href="#"> 멤버소개</a>

          </li>

        </ul>

      </li>


    </ul>

  </div><!--/.nav-collapse -->

</div>


 다시 "grunt serve"를 통하여 메뉴가 정상적으로 만들어 졌는지 확인한다 



3. 메뉴 링크에 라우팅 설정하기 

  메뉴구조를 만들었으니 이제 UI-Router(https://github.com/angular-ui/ui-router) 방식에 따라 라우팅을 설정해 보자. 앵귤러 라우팅 순서는 먼저 서버에서 HTML파일을 불러온 후 앵귤러에서 HTML파일을 파싱하여 앵귤러 코드를 앵귤러 컨텍스트에 포함시키고 DOM을 변경한다. 

  

  - 서버에 HTML 파일 요청하기 

  - HTML 파일 파싱하여 앵귤러 컨텍스트에 포함시키기

  - DOM을 변경하여 화면에 표현하기 


먼저 "My Level: 평민" 메뉴를 대상으로 설정을 하면 나머지는 동일 과정을 반복하게 된다. 

1) mylevel.html 파일을 app/views 폴더 밑에 생성한다 

<div class="row">

  <div class="col-md-12">

    <h1>mylevel.html</h1>

  </div>

</div>


2) <li> 태그에 현재 선택이 되면 class="active" 부트스트랩 클래스를 추가하기 위하여 앵귤러의 ng-class를 사용한다. 

  - 라우킹 명칭을 "mylevel" 로 정한다 

  - (data-)ng-class 안에 표현식을 넣어서 현재 라우팅 명칭이 mylevel (true)이면 active 클래스를 적용하고 false이면 적용하지 않는다

  - ui-sref 는 ui-router에서 사용하는 라우팅 변경 링크 속성으로 href를 대체한다 

// 기존 설정

<li>

    <a href="#"> My Level: 평민</a> 

</li>


// 변경 설정 

<li data-ng-class="{ active: $state.includes('mylevel') }">

   <a ui-sref="mylevel"> My Level: 평민</a> 

</li>

  

3) 이제 HTML 파일의 위치와 기타 정보를 설정한다

  - app/scripts/app.js 가 meanstack 서비스의 메인 애플리케이션 파일이 된다. 앵귤러가 최초 수행이 되면 애플리케이션에서 사용하는 $rootScope를 만들게 되는데 app.js 메인 애플리케이션 레벨에서 사용하는 Global Scope 이다. 단 브라우져를 재로딩하면 초기화 된다. 

  - ui-router 설정은 홈페이지(https://github.com/angular-ui/ui-router)를 참조한다.

'use strict';


angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap',

  'ui.router'

])

.config(['$stateProvider', '$urlRouterProvider',  function ($stateProvider, $urlRouterProvider) {


    $urlRouterProvider.otherwise("/");


    $stateProvider

      .state('main', {

        url: '/',

        templateUrl: 'views/main.html',

        controller: ''

      })

      .state('mylevel', {

        url: '/mylevel',

        templateUrl: 'views/mylevel.html',

        controller: ''

      });

  }])


  - $urlRouterProvider.otherwise('/') 는 설정되지 않은 uri 요청이 들어올 경우를 처리한다. 여기서는 main 으로 간다 

  - main.html은 meanstack을 만들 때 생성된 html이다 

  - 메뉴에서 "나의 레벨"을 클릭하면 "mylevel" state의 url로 변경되고 templateUrl에 설정한 html을 서버에 요청한다 

  - HTML을 파싱하여 앵귤러 컨텍스트에 포함시키고 앵귤러 코드에 대한 처리는 controller에서 담당하나 아직은 설정하지 않았다

  - mylevel.html의 파싱된 DOM은 index.html의 "<div ui-view class="container"></div>" ui-view 하위 <div>에 자동 주입된다. 


설정이 완료되었으면 "나의 레벨" 메뉴를 클릭해 본다 


ui-view 속성이 있는 <div>에 mylevel.html 파일의 내역의 표현이 약간 위로 올라갔다. index.html과 main.css를 수정한다 

// app/index.html 기존 

<div class="navbar navbar-inverse navbar-fixed-top" ng-include="' /views/menu.html '"></div>


// app/index.html 수정

<div class="navbar navbar-default navbar-static" ng-include="' /views/menu.html '"></div>


// app/views/main.html 수정 

<div class="row">

  <div class="col-md-12">

    <h1>main.html</h1>

    <h2> Welcome to MEANStack.net</h2>

  </div>

</div>


// app/sytles/main.css 의 모든 내역을 삭제함 


  최종 결과 화면




4. 메뉴에 적절한 아이콘 설정하기 

 다른 메뉴들도 라우팅 명칭을 정하고 menu.html과 app.js에 라우팅 설정을 하고 각 메뉴의 기본 파일을 app/views/ 폴더 밑에 생성하여 연결한다. 그리고 메뉴에 FontAwesome의 아이콘을 설정한다. 


  - FontAwesome 사이트에서 원하는 아이콘을 선택한다 : http://fortawesome.github.io/Font-Awesome/icons/

  - 아이콘 태그를 넣는다 : 예) <i class="fa fa-check-square"></i> fa-check-square

// app/views/menu.html

<div class="container">


  <div class="navbar-header">

    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">

      <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span>

    </button>

    <a ui-sref="main" class="navbar-brand"><i class="fa fa-th-large"></i> MEANStack.net</a> 

  </div>

  

  <div class="collapse navbar-collapse">

    <ul class="nav navbar-nav">

      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">

          <i class="fa fa-spinner fa-spin"></i> My Area <b class="caret"></b>

        </a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Status</li>

          <li data-ng-class="{ active: $state.includes('mylevel') }">

            <a ui-sref="mylevel"><i class="fa fa-eye"></i> My Level: 평민</a> 

          </li>

          <li data-ng-class="{ active: $state.includes('myqna') }">

            <a ui-sref="myqna"><i class="fa fa-comments"></i> 질문: 3, 답변: 1</a>

          </li>

          

          <li class="divider"></li>

          <li class="dropdown-header">Following</li>

          <li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '1' }">

            <a ui-sref="myfollowing({userId:'1'})"><i class="fa fa-thumb-tack"></i> 박유진</a>

          </li>

          <li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '2' }">

            <a ui-sref="myfollowing({userId:'2'})"><i class="fa fa-thumb-tack"></i> 이규원</a>

          </li>

          <li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '3' }">

            <a ui-sref="myfollowing({userId:'3'})"><i class="fa fa-thumb-tack"></i> 윤영식</a>

          </li>


        </ul>

      </li>


      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">

        <i class="fa fa-pencil-square-o"></i> Tech Area <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">SPA</li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '1' }">

            <a ui-sref="techarea({techId:'1'})"><i class="fa fa-file-text-o"></i> Angular.js</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">Middleware</li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '2' }">

            <a ui-sref="techarea({techId:'2'})"><i class="fa fa-file-text-o"></i> Node.js</a>

          </li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '3' }">

            <a ui-sref="techarea({techId:'3'})"><i class="fa fa-file-text-o"></i> Express.js</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">NoSQL</li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '4' }">

            <a ui-sref="techarea({techId:'4'})"><i class="fa fa-file-text-o"></i> MongoDB</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">Eco Tools</li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '5' }">

            <a ui-sref="techarea({techId:'5'})"><i class="fa fa-file-text-o"></i> Yeoman</a>

          </li>

        </ul>

      </li>


      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">

        <i class="fa fa-smile-o"></i> GuruGuru <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Ranking</li>

          <li data-ng-class="{ active: $state.includes('gururanking') }">

            <a ui-sref="gururanking"><i class="fa fa-sitemap"></i> 명예의 전당</a>

          </li>

          <li class="divider"></li>

          <li class="dropdown-header">Members</li>

          <li data-ng-class="{ active: $state.includes('gurumember') }">

            <a ui-sref="gurumember"><i class="fa fa-users "></i> 멤버소개</a>

          </li>

        </ul>

      </li>

    </ul>

  </div>

</div> 



// app/scripts/app.js 

angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap',

  'ui.router'

])

.config(['$stateProvider', '$urlRouterProvider',  function ($stateProvider, $urlRouterProvider) {


    $urlRouterProvider.otherwise("/");


    $stateProvider

      .state('main', {

        url: '/',

        templateUrl: 'views/main.html',

        controller: ''

      })

      .state('login', {

        url: '/login',

        templateUrl: 'views/login.html',

        controller: ''

      })

      .state('mylevel', {

        url: '/mylevel',

        templateUrl: 'views/mylevel.html',

        controller: ''

      })

      .state('myqna', {

        url: '/myqna',

        templateUrl: 'views/myqna.html',

        controller: ''

      })

      .state('myfollowing', {

        url: '/myfollowing/{userId:[0-9]{1,4}}',

        templateUrl: 'views/myfollowing.html',

        controller: ''

      })

      .state('techarea', {

        url: '/techarea/{techId:[0-9]{1,4}}',

        templateUrl: 'views/techarea.html',

        controller: ''

      })

      .state('gururanking', {

        url: '/gururanking',

        templateUrl: 'views/gururanking.html',

        controller: ''

      })

      .state('gurumember', {

        url: '/gurumember',

        templateUrl: 'views/gurumember.html',

        controller: ''

      });


  }])


  - ui-router에서 myfollowing({userId:'1'}) 는 파라미터를 넘기는 방식이다 형식) ui-sref='stateName({param:value, param:value})

  - ui-router API : https://github.com/angular-ui/ui-router/wiki/Quick-Reference


현재까지 진행된 내역을 GitHub에 올려 놓았다. 다음은 router에 앵귤러 controller를 만들고 router에 설정해 보자 

https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/angular_step01_making-index


posted by Peter Note
2014. 1. 29. 07:48 AngularJS/Start MEAN Stack

Git, Node.js, Yeoman등 기본적인 개발환경을 설치하였다면 소프트웨어 요구사항 정의서 (Software Requirements Specification, SRS) 에서 정의한 화면을 CSS Framework을 사용하여 만들어 보자. 여기서는 가장 많이 사용하고 있는 Twitter Bootstrap을 사용토록 한다. 



사전준비

  이전 글에서 yeoman의 yo 명령을 통하여 angular 프로젝트를 "meanstack"이름으로 생성하였다. meanstack 하위에 app 폴더가 앵귤러 기반의 SPA (Single Page Application) 프론트앤드 애플리케이션의 ROOT 폴더가 된다. 


  - 반응형 웹 디자인 기반의 메뉴구조를 구성한다 

  - 메뉴를 클릭하면 해당하는 html 파일로 라우팅한다 

  - 메뉴에 아이콘을 추가한다 




1. Twitter Bootstrap 설치

 Twitter Bootstrap은 meanstack을 생성하면서 app/index.html에 자동 추가된다. 

  1) <!-- build:css ... --> : 주석은 제거하면 안된다. grunt build 시에 css 압축할 때 사용하는 주석이다. (build:js 도 동일) 

  2) <!-- bower:css .. --> : 주석은 제거하면 안된다. grunt build 시에 자동으로 bower install 한 내역을 추가한다. (bower:css 도 동일) 

// index.html

    <!-- build:css styles/vendor.css -->

    <!-- bower:css -->

    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />

    <!-- endbower -->

    <!-- endbuild -->


   .. 중략 ..


    <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <!-- endbower -->

    <!-- endbuild -->

  * "yo angular meanstack" 명령으로 twitter bootstrap이 자동 설치가 되면 twitter bootstrap이 사용하는 fonts 폴더를 app/fonts로 자동 복사해 준다. 이것은 grunt build시에 모든 css가 app/styles/*.css로 압축되고 bootstrap의 css가 사용하는 font는 ../fonts를 참조하기 때문이다. 따라서 배포를 위해서는 app/fonts 폴더가 존재 해야한다.




2. Angular UI-Bootstrap 설치

  Twitter Bootstrap을 html에 적용하려면 class 정보를 입력해야 한다. 이를 좀 더 직관적인 html tag 형식으로 쓸 수 있도록 AngularUI 팀에서 Directives를 만들었다. 예로 tab을 적용하가 위하여 <tab> 태그를 html에 사용하면 angular-bootstrap 모듈이 <tab> 를 class="tab" 형태로 바꿔서 DOM에 적용하는 것이다. html을 좀 더 직관적으로 작성할 수 있다는 장점이 있다. 

$ bower install angular-bootstrap --save

angular-bootstrap#0.10.0 app/bower_components/angular-bootstrap

└── angular#1.2.10


  bower를 통해 angular-bootstrap을 설치 후 "grunt build" 또는 "grunt serve" 를 실행하면 index.html에 필요 파일이 자동 추가된다. 자동 추가되는 파일의 정보는 bower_components/angular-bootstrap/bower.json 파일에 지정되어 있다.

// index.html 파일 

   <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>

    <!-- endbower -->

    <!-- endbuild -->


  angular-bootstrap은 별도의 앵귤러 모듈이므로 애플리케이션에서 사용하려면 모듈 의존성을 설정해야 한다. meanstack 서비스의 메인 소스는 app/scripts/app.js 이다. 여기에 모듈 의존성을 설정한다. 

  - 형식 : angular.module('<ApplicationName>', [<의존성 모듈 열거>]);

// app/bower_components/angular-bootstrap/ui-bootstrap-tpl.js 파일 상단

angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);



// app/scripts/app.js 안에 추가 

'use strict';

angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap'

])

  .config(function ($routeProvider) {

    .. 중략 ..

  });



3. Angular UI-Router 설치 

클라이언트 화면 전환을 위한 라우팅 기능은 앵귤러에서 제공하는 기본적인 라우터를 사용하지 않고 AngularUI팀에서 역시 제공하는 ui-router (https://github.com/angular-ui/ui-router)를 사용한다. ui-router는 한 화면에서 멀티 view 설정으로 원하는 부분들의 DOM을 변경처리할 수 있다. bower를 통해 ui-router v0.2.7 버전을 설치한다. ui-router v0.2.8 버전설치에 오류가 존재하기 때문이다. 또한 설치전에 Angular.js 버전을 현재 (2014.1.28기준) 최신버전인 v1.2.10 으로 업데이트한다.

// bower.json 파일에서 angular 관련 버전을 1.2.10 으로 지정한다 

$ vi bower.json

{

  "name": "meanstack",

  "version": "0.0.0",

  "dependencies": {

    "angular": "1.2.10",

    "json3": "~3.2.6",

    "es5-shim": "~2.1.0",

    "jquery": "~1.10.2",

    "bootstrap": "~3.0.3",

    "angular-resource": "1.2.10",

    "angular-cookies": "1.2.10",

    "angular-sanitize": "1.2.10",

    "angular-route": "1.2.10",

    "angular-ui-router": "0.2.7"

  },

  "devDependencies": {

    "angular-mocks": "1.2.10",

    "angular-scenario": "1.2.10"

  }

}


// angular 버전 업데이트

$ bower update 

angular-resource#1.2.10 app/bower_components/angular-resource

└── angular#1.2.10

.. 중략..

angular-scenario#1.2.10 app/bower_components/angular-scenario

└── angular#1.2.10


// ui-router 설치 1번을 선택한다 

$ bower install angular-ui-router#0.2.7 --save

    1) angular-ui-router#0.2.7 which resolved to 0.2.7

    2) angular-ui-router#~0.2.8 which resolved to 0.2.8 and has meanstack as dependants

[?] Answer: 1

angular-ui-router#0.2.7 app/bower_components/angular-ui-router

└── angular#1.2.10


   bower를 통해 ui-router를 설치 후 "grunt build" 또는 "grunt serve" 를 실행하면 index.html에 필요 파일이 자동 추가된다. 자동 추가되는 파일의 정보는 bower_components/angular-bootstrap/bower.json 파일에 지정되어 있다. 

    <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>

    <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>

    <!-- endbower -->

    <!-- endbuild -->


  ui-router 또한 별도의 모듈이므로 app/scripts/app.js 안에 모듈 의존성을 설정한다 

'use strict';


angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap',

  'ui.router'

])

  .config(function ($routeProvider) {

  .. 중략 ..

  });



4. FontAwesome Icon 설치

  다양한 아이콘을 사용하기 위하여 bootstrap과 별도로 fontawesom(http://fortawesome.github.io/Font-Awesome/) 라이브러리를 설치한다. 텍스트 정보를 좀 더 직관적으로 인지할 수 있도록 다양한 아이콘을 적용할 수 있다. 

$ bower install font-awesome --save

bower font-awesome#*            cached git://github.com/FortAwesome/Font-Awesome.git#4.0.3

font-awesome#4.0.3 app/bower_components/font-awesome


  설치 후 app/bower_components/font-awesome/ 폴더 밑으로 bower.json 이 존재하지 않으므로 필요한 파일을 index.html에 직접 기입해야 한다. index.html에 font-awesome.css 파일을 추가하자. 추가시 <!-- bower:css --> 영역에 넣으면 grunt build(grunt serve)시에 직접 넣은 정보는 자동 삭제되므로 <!-- bower:css --> 가 없는 <!-- build:css --> 안에 추가한다. 

     <!-- build:css styles/vendor.css -->

    <!-- bower:css -->

    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />

    <!-- endbower -->

    <!-- endbuild -->


    <!-- build:css({.tmp,app}) styles/main.css -->

    <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css" />

    <link rel="stylesheet" href="styles/main.css">

    <!-- endbuild -->


  'grunt build'를 하게 되면 font-awesome의 css는 app/styles/main.css 파일로 묶이게 된다. font-awesome.css 소스를 보면 css 상위의 fonts 폴더의 파일을 참조한다. 따라서 font-awesome의 fonts 폴더안의 파일들을 app/fonts 밑으로 복사한다.

Twitter Bootstrap과 Font awesome의 css가 참조하는 파일들 목록

$ cd app/fonts

$ ls 


fontawesome-webfont.woff

fontawesome-webfont.ttf

fontawesome-webfont.svg

fontawesome-webfont.eot

FontAwesome.otf

glyphicons-halflings-regular.woff

glyphicons-halflings-regular.ttf

glyphicons-halflings-regular.svg

glyphicons-halflings-regular.eot


이제 필요한 라이브러리들이 index.html에 추가되었다. 다음장에서는 Twitter Bootstrap을 이용하여 반응형 웹 디자인(Responsive Web Design : RWD) 메뉴와 전체 화면 레이아웃을 만들어 보자.

posted by Peter Note
2014. 1. 26. 07:50 AngularJS/Start MEAN Stack

yeoman은 자바스크립트 기반의 프론트앤드 개발을 도와주는 자동화 툴이다. 자바에서 요즘 많이 사용되는 Maven과 유사하다. 



1. 사용이유

  서비스 개발의 규모가 커지고 자바스크립트의 각 모듈별의 버전 의존성을 관리하려면 어떻게 해야 할까? 그리고 필요한 자바스크립트 모듈을 다운로드하기 위하여 각 사이트를 방문하여 다운로드 해야할까? 기본적인 코드 골격과 코딩 컨벤션은 최초에 어떻게 해야할까? 개발하고 나서 테스트 및 빌드 자동화는 어떻게 해야할까? 이러한 의문에 대한 해답을 주는 것이 Yeoman이다.

  - 스케폴딩 생성 : 기본적인 코드 골격을 만들어 주고 이후 추가되는 앵귤러 코드는 명령을 통해 생성한다 

  - 라이브러리 의존성 관리 : 프론트앤드에서 사용하는 다양한 jquery 모듈과 angular.js 모듈의 설치 및 버전 관리를 한다 

  - 테스트 및 빌드 자동화 : 테스트 코드를 작성하였다면 테스트 수행 및 코드 압축과 문법오류 검사 그리고 배포파일의 생성을 한다 

  - 사전 점검 : 프론트단의 코드를 Node.js기반 수행하여 브라우져에서 개발한 화면을 사전에 테스트 점검 가능하다



2. 설치하기  

  설치는 의외로 간단하다. 사전 준비로 Node.js를 설치 하였다면 NPM (Node Package Manager)을 통해 설치한다. yo, bower, grunt 의 특징은 자바스크립트로 개발하여 node.js기반위애 구동하는 도구이다 

// 1) yo 설치 

$ npm install -g yo


// 2) bower 설치 

$ npm install -g bower


// 3) grunt 설치 

$ npm install -g grunt-cli

  * http://yeoman.io, http://bower.iohttp://gruntjs.com 에서 설치관련 자세한 사항을 참조한다. 



3. 주요 기능 

  yeoman은 크게 3가지 기능으로 구분된다. 각 기능의 구분이 명확하고 사용하는 목적이 틀리기 때문에 잘 알아 두기 바란다. 기본적으로 yo를 yeoman팀에서 자체적으로 만들었고, bower와 grunt는 이미 존재하였던 도구로써 yeoman이라는 이름으로 통합한 것이다. 따라서 bower와 grunt를 별개의 독립적인 도구로 사용할 수도 있다


  yo 

  - 프론트앤드 개발을 위한 기본 구조를 만들어 준다. yeoman에서는 스켈폴딩 코드를 만들어 준다고 말한다 

  - 스케폴딩 파일을 생성하려면 목적에 맞는 제너레이터를 사전에 설치해야 한다.

    1) 제너레이터 명칭 구성 : generator-<UserDefine> 로서 UserDefine 명칭을 정한다. 예) generator-angular

    2) npm 을통하여 글로벌 설치한다 

    3) UserDeinfe 명칭이 angular 라면 yo 명령 다음에 제너레이터의 구분을 위하여 항시 해당 UserDefine 명령이 온다 

        yo angular <Subject>  또는 yo angular:<SubCommand>  <Subject>

    4) yo angular <Subject> 를 통해 초기 프로젝트의 명칭을 정하고 기본 골격을 생성한다 

    5) yo angular:<SubCommand> <Subject> 를 통해 기본골격 밑으로 기능을 추가한다. SubCommand는 제너레이터에 따라 사용자 정의 할 수 있다 

  - 프로젝트에 필요한 골격 코드 생성을 위한 자신만의 제너레이터를 만들 수 있다. 

  - 사용법

    1) angular 제너레이터 설치

    2) 프로젝트 폴더생성

    3) angular 프로젝트 생성을 하고 Sass 미사용, Bootstrap을 사용체크하면 필요한 파일과 모듈을 자동 설치한다

    4) 프로젝트 기본 골격코드 자동 생성하면 app 폴더가 프론트 개발 ROOT 폴더가 된다

// 1) 

$ npm install -g generator-angular


// 2) 

$ mkdir meanstack & cd meanstack


// 3) 

$ yo angular meanstack

     _-----_

    |       |

    |--(o)--|   .--------------------------.

   `---------´  |    Welcome to Yeoman,    |

    ( _´U`_ )   |   ladies and gentlemen!  |

    /___A___\   '__________________________'

     |  ~  |

   __'.___.'__

 ´   `  |° ´ Y `


Out of the box I include Bootstrap and some AngularJS recommended modules.


[?] Would you like to use Sass (with Compass)? No

[?] Would you like to include Twitter Bootstrap? Yes

[?] Which modules would you like to include? (Press <space> to select)

❯⬢ angular-resource.js

 ⬢ angular-cookies.js

 ⬢ angular-sanitize.js

 ⬢ angular-route.js


// 4) 


bower

  - bower는 트위터에서 개발한 프론트앤드 라이브러리 설치 및 버전 의존성 관리 도구이다 

  - yo를 통하여 스케폴딩 파일이 생성하면 bower 사용을 위한 기본 환경파일도 자동 생성된다 

    1) 환경파일은 bower.json 으로 현재 설치된 라이브러리 명칭과 버전을 자동 기록한다 

    2) 라이브러리 설치 위치정보는 .bowerrc 에서 변경한다. 기본값으로 "bower_components" 를 사용한다 

// 1) bower.json 최초 설치 정보 

{

  "name": "meanstack",

  "version": "0.0.0",

  "dependencies": {

    "angular": "1.2.6",

    "json3": "~3.2.6",

    "es5-shim": "~2.1.0",

    "jquery": "~1.10.2",

    "bootstrap": "~3.0.3",

    "angular-resource": "1.2.6",

    "angular-cookies": "1.2.6",

    "angular-sanitize": "1.2.6",

    "angular-route": "1.2.6"

  },

  "devDependencies": {

    "angular-mocks": "1.2.6",

    "angular-scenario": "1.2.6"

  }

}


// 2) .bowerrc 위치정보

{

    "directory": "app/bower_components"

}


  - 사용법 

   1) 검색 : "bower search <명칭>"

   2) 설치 : "bower install <명칭>  --save (또는 --save-dev)"

   3) 보기 : "bower list"

   3) 도움말 : "bower help" 또는 "bower help <명령어>"

$ bower list

bower check-new     Checking for new versions of the project dependencies..

meanstack#0.0.0 ~/meanstack

├── angular#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

├─┬ angular-cookies#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

├─┬ angular-mocks#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-resource#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-route#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-sanitize#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-scenario#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ bootstrap#3.0.3

│ └── jquery#1.10.2 (2.1.0 available)

├── es5-shim#2.1.0 (latest is 2.3.0)

├── jquery#1.10.2 (latest is 2.1.0)

└── json3#3.2.6 (latest is 3.3.0)

  * angular 개발시점에 정식 릴리즈된 가장 최신버전을 사용할 예정이고 bower를 통하여 업데이트할 것이다. 


Grunt

  - grunt는 자바의 ant와 같은 기능을 수행한다

  - 다양한 플러그인을 통하여 기능을 첨부하여 확장할 수 있다. 

  - yo을 통해 프로젝트 골격 코드생성시 Gruntfile.js 환경파일이 기본 생성된다 

  - 또한 골격 코드 생성시 기본적으로 사용되는 플러그인은 node_moudles 폴더에 자동 설치된다 

// 1) node_modules에 기본 설치된 grunt 플로그인 


// 2) Gruntfile.js 내역 중 명령 일부

module.exports = function (grunt) {

  // Load grunt tasks automatically

  require('load-grunt-tasks')(grunt);


  // Time how long tasks take. Can help when optimizing build times

  require('time-grunt')(grunt);


  // Define the configuration for all the tasks

  grunt.initConfig({


    // Project settings

    yeoman: {

      // configurable paths

      app: require('./bower.json').appPath || 'app',

      dist: 'dist'

    },

    ... 중략...


grunt.registerTask('serve', function (target) {

    if (target === 'dist') {

      return grunt.task.run(['build', 'connect:dist:keepalive']);

    }


    grunt.task.run([

      'clean:server',

      'bower-install',

      'concurrent:server',

      'autoprefixer',

      'connect:livereload',

      'watch'

    ]);

  });


  grunt.registerTask('server', function () {

    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');

    grunt.task.run(['serve']);

  });


  grunt.registerTask('test', [

    'clean:server',

    'concurrent:test',

    'autoprefixer',

    'connect:test',

    'karma'

  ]);


  grunt.registerTask('build', [

    'clean:dist',

    'bower-install',

    'useminPrepare',

    'concurrent:dist',

    'autoprefixer',

    'concat',

    'ngmin',

    'copy:dist',

    'cdnify',

    'cssmin',

    'uglify',

    'rev',

    'usemin',

    'htmlmin'

  ]);


  grunt.registerTask('default', [

    'newer:jshint',

    'test',

    'build'

  ]);

};


  - 사용법

   1) 테스트 : grunt test

   2) 빌드 : grunt build

   3) 프리뷰 : grunt serve

// 1) 

$ grunt test

Running "clean:server" (clean) task


Running "concurrent:test" (concurrent) task


    Running "copy:styles" (copy) task

    Copied 1 files


    Done, without errors.



    Execution Time (2014-01-25 22:42:19 UTC)

    loading tasks   4ms  ▇▇▇▇ 25%

    copy:styles    10ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 63%

    Total 16ms


Running "autoprefixer:dist" (autoprefixer) task

Prefixed file ".tmp/styles/main.css" created.


Running "connect:test" (connect) task

Started connect web server on 127.0.0.1:9001.


Running "karma:unit" (karma) task

INFO [karma]: Karma v0.10.9 server started at http://localhost:8080/

INFO [launcher]: Starting browser Chrome

WARN [watcher]: Pattern "/Users/nulpulum/prototyping/yeomain/meanstack/test/mock/**/*.js" does not match any file.

INFO [Chrome 32.0.1700 (Mac OS X 10.9.1)]: Connected on socket nUaNX14YFrQJ_aeDkWM5

Chrome 32.0.1700 (Mac OS X 10.9.1): Executed 1 of 1 SUCCESS (2.184 secs / 0.033 secs)


Done, without errors.



Execution Time (2014-01-25 22:42:17 UTC)

concurrent:test  2.2s  ▇▇▇▇▇▇▇▇▇▇▇26%

karma:unit       6.2s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 73%

Total 8.4s


// 2) 브라우져가 자동 실행된다 

$ grunt serve 


Yeoman에 대한 이해와 기본적인 설치, 사용법을 익혔다. 프론트 앤드 개발을 하면서 계속 사용하게 되므로 잘 익혀 놓도록 하자 


posted by Peter Note
2014. 1. 2. 13:06 AngularJS/Start MEAN Stack

개인 블로그를 통하여 정보를 공유하는 일에서 한발 더 나아가 함께 글을 올리고 글에 대한 Q&A를 하는 웹앱 서비스를 만들어 보고 싶다. 내가 로그인을 했을 때 처음 보여지는 UI는 SNS 형태이며 내가 올린 질문(Question)에 대한 답변이나 커뮤니티안에서 활동하는 재능 기부자를 팔로잉 하면 해당 글들이 먼저 보여졌으면 좋겠다. 즉, 글에 대한 개인화를 원하며 재미있는 재능 기부 놀이를 해보고 싶다. 




1. 기존 커뮤니티 서비스

  - 특정 재능 기부자가 글을 연재하여 올린다

  - 여러 Q&A 게시판 형태를 취한다  

  - 내가 원하는 것을 Search 하여 찾아 보아야 한다 

  - PC에 최적화 되어있다. 어떤 것은 모바일에서 볼 수도 있다

   글올렸으니 알아서 찾아 읽어봐 ^^;



2. MEAN Stack 커뮤니티 서비스 

  - SNS 형태를 취하여 모바일기기(Tab, Phone) 중심으로 만들어진다 

  - MEAN Stack에 대한 글을 연재하는 재능 기부 개발자들이 있고, 이중 몇명을 following 할 수 있다.

  - 내가 연재글이나 별도의 란에 질문(Question)을 달면 MEAN Stack 재능 기부 개발자들에게 자동 Notify가 된다. 

    (단, 별도의 질문은 지정된 분류만을 할 수 있다. Concern Area)

  - 답변을 준 재능 기부자에게 점수를 통해 고마움을 표현하면 해당 점수는 재능 기부자의 인기도 순위에 영향을 준다.

  - 또한 연재 글에 대해서도 모든 사람은 한번 점수를 줄 수 있다.

  - 인기도는 천민, 일반, 귀족, 왕족, 신(구루) 등의 구분으로 명예의 전당에 공개되어 구루로 추앙된다. 게임처럼 놀고 싶다. 

  - 해당 인기도는 한달에 한번 reset되고 이달의 구루에겐 모종의 선물을 수여한다.(뭘 주지? ^^)

  - 즉, 답변이나 글연재를 많이 한다고 구루가 되지 않고 해당 답변, 글에 메겨진 점수를 통해 인기도 순위와 등급은 결정된다 

  - 가입한 누구나 특정 분류의 재능 기부자가 될 수 있다 

   재능 기부하면서 함께 재미 있게 놀고 싶다. 



3. Mockup

  - 서비스의 명칭 

    + Community Planet : 새로운 혹성의 커뮤니티. 점수통해 자신만의 영역을 만들어 간다. 우리는 디지털 이주민.

      

  - 메인 화면 

    + 좌측은 모바일의 Sliding 메뉴로 구성된다 

    + 로그인을 하면 우측 상단에 Push Message 건수와 로그인/로그아웃 링크가 나온다 

    + 중앙에는 내가 질문한 것에 대한 답변과 다른 질문/답변들 그리고 Following 한 재능 기부자들의 글목록이 나온다

    + 글 목록은 핀터레스트 스타일 이다 


  - Question & Answer

    + 내가 질문한 것과 답변 받은 것을 확인 할 수 있다 

    + 질문내역을 클릭 하면 질문에 대한 답변을 볼 수 있다. 

    + 답변에 대한 점수를 줄 수 있고, 답변이 틀리면 마이너스 점수도 줄 수 있다 


  - Following 재능 기부자들의 글 보기

    + 팔로어한 사람별의 최근 글을 볼 수 있다. 내가 관심갖는 글만 보기 

    + 보는 방식은 Q&A화면과 유사하다 

    + 연재글에 역시 질문과 답을 달 수 있다 

 

  - Concern Area

    + 관심영역은 현재 커뮤니티 서비스 관리화면에서 목록을 만들 수 있다. (마치 워드프레스의 관리화면처럼-향후 개발)

    + 특정 관심 영역을 들어가면 해당 영역에서 활동하는 모든 글들을 볼 수 있다.

    + 검색하여 글을 찾을 수 있다 


  - 구루구루

    + 이곳은 명예의 전당이다 

    + 가장 점수가 높은 재능 기부자를 상위에 놓고 점수가 높은 순으로 글목록이 나열 된다 

    + 그리고 이번달의 글과 질문 답변에 대해 볼 수 있다 



다음의 비디오를 참조 했으면 좋겠다. Community Planet은 바로 이 정신이다. 


posted by Peter Note
2013. 12. 15. 13:38 AngularJS/Start MEAN Stack

개발자들이 가장 힘들어 하는 부분은 UI 개발이라 생각한다. 더 나아가 UX(User eXperience)를 어떻게 UI속에 녹여내어 편의성과 제품의 가치에 맞는 감성을 넣을 수 있을지도 고민해 보아야 한다. 막상 클라이언트 개발을 시작하였지만 마땅히 디자이너의 도움 없이도 완성도 있는 서비스를 만들고 싶다면 CSS Framework을 사용해 보자. 


요즘 추세는 크게 두가지 측면으로 움직이는 것 같다. 하나는 웹의 모양과 색감등 보여지는 부분을 담당하는 CSS를 프레임워크화하여 일관된 UX를 전달해주고 있고 이를 공개하고 있다. 이중 가장 유명한 것이 트위터 부트스트랩이다. 버전 3.0 에서는 반응형 웹 디자인(RWD, Responsive Web Design)이 기본으로 적용되어 모바일 스마트기기의 해상도에 따라 화면이 최적화되어 표현될 수 있도록 하는 기능을 제공한다. 둘째는 플랫디자인이다. 구글이나 애플에서 주도를 하고 있고 간결하면서 사용성을 늘린 디자인으로 보인다. 기본 트위터 부트스트랩을 확장하여 플랫디자인 CSS를 적용한 다양한 CSS 프레임워크를 제공하고 있다. 


따라서 트위터 부트스트랩을 기본으로 하여 확장한 CSS 프레임워크를 사용하게 되면 '반응형 웹 디자인' 과 '플랫디자인'을 동시에 충족하여 적용할 수 있게 된다. 두가지 요소에 대해서는 이곳의 주제와 약간 벗어나므로 좀 더 전문적인 서적을 통해 개념과 상세 기능을 익히기 바란다. 여기서는 선택한 CSS 프레임워크를 어떻게 적용하고 활용하는지 중점적으로 보도록 하겠다. 



1. 반응형 웹 디자인 고려

  - Fluid Layout : 화면 사이즈에 따라 배치된 요소들의 정렬을 재 배치하는 것이다. 화면이 작아진다고 하여 좌우 스크롤이 생기는 것이 아니라 요소가 밑으로 흘러들어 간다. 이를 위하여 부트 스트랩은 Grid 12 컬럼으로 요소 배치 및 플루이드 정렬을 할 수있다 

  - Responsive Menu : 부트 스트랩은 상단에 메뉴를 놓고 해상도에 따라 메뉴의 형태를 자동 변경해 준다 

서비스 개발이 위의 두가지를 먼저 유념하여 사용하도록 한다. 기본적인 사용법은 트위터 부트스랩의 홈페이지를 참조하자 



2. 플랫 디자인에 대한 고려

애플은 iOS7에서 플랫디자인을 선보이고, 구글은 전사 서비스를 플랫디자인으로 도배하고 있습니다. 프러덕트 디자이너가 Visual 단계에서 바라본 플랫디자인의 요소 5가지를(참조) 유념하여 개발자가 UI와 UX를 플랫 디자인으로 진행할 때 고려해야할 사항에 대해 미리 알아두면 좋을 것 같습니다.

     + Use of simple elements 

        심플한 아이콘 : 사각, 원형의 단순 모형에 배치, 컬러, 형태로 표현 -> 사용자가 UI 를 쉽게 인식하고 사용토록 만든다 

     + Absence of depth 

        배제된 효과 : 그림자, 입체감, 그라데이션등의 현실감있게 하는 3D적 효과를 배제하여 직관적으로 만든다  

     + Typography

        타이포그래피 : 화려한 폰트를 배제하고 간결한 폰트를 사용한다. 예) 산세리프 

     + Color

        단순한 칼라 : 2~3가지 또는 5~6가지의 색상만을 사용하여 간결하고 과감하고 표현한다. 핑크그린블루가 대세  

     + Minimalism

        미니멀리즘 : 미니멀리즘과 부합 - 간단하고 심플하게 표현으로 다양한 웹, 모바일에 적응이 쉽다  



3. 서비스 디자인으로 나아가야 한다 

  서비스을 만들 때 개발자는 어떻게 접근을 하고 있을까요? 아마도 대부분 디자이나 웹 퍼블리셔가 만들어준 화면에 코딩을 하는 정도를 경험했으리라 봅니다. 하지만 자신의 웹앱 서비스를 만들기 시작한다면 단지 디자인이 문제가 아니라 UX라는 사용자 경험을 고려하여 사람과 컴퓨터의 상호작용을 생각해 보아야 하고, 우리가 만들려는 프로덕트의 가치를 서비스에 어떻게 녹여 내야 하는지 연구하고 새롭게 창조하는 과정을 거쳐향 합니다. 이를 줄이면 서비스 디자인이라고 말하며 서비스 개발전 우리가 사용하는 도구와 프레임워크들이 가치를 창출하기 위하여 어떻게 쓰여야하는지 큰 뷰에서 설명을 해주고 있습니다. 인식의 전환을 통하여 중요한 가치를 찾고 반응형 웹 디자인이나 플랫 디자인이 왜 나왔는지 이해하여 보는 것도 재미날 것같습니다. 한국디자인진흥원의 윤성원님이 이야기하는 서비스 디자인 개념 자료를 한번 보시면 쉽게 이해 되리라 생각합니다. 


  - 서비스 디자인 이란 무엇인가? - 개념 이해


posted by Peter Note
2013. 12. 5. 13:34 AngularJS/Start MEAN Stack

이전 블로그에서 MEAN Stack에 대한 의미를 알아보았습니다. Angular.js 의 Why, How, What을 살펴보도록 하겠습니다.


1. Angular.js 가 주고자 하는 가치는 무엇인가?

  앵귤러는 모던 애플리케이션을 개발하기 위한 프레임워크입니다. 복잡하고 단순 반복적인 작업을 대폭 줄여줌으로써 신속한 개발을 가능하게 해줍니다. 이를 통해 보다 빨리 고객의 피드백을 받고 개선할 수 있는 에자일한 개발 진행이 가능해 집니다. 보다 적은 작업을 통해 보다 더 많은 행복을 주는 프레임워크입니다. 


  1989년 HTML을 시작으로 2005년 Ajax가 나오고 이후 jQuery를 통하여 DOM 을 조작을 통한 웹서비스 개발 시대를 거쳐 지속적인 브라우져 기술의 성숙과 JavaScript 해석기의 성능향상으로 이제는 자바스크립트를 통해 클라이언트에서도 서버와 같은 MVC 패턴 방식의 애플리케이션 개발이 가능해 졌습니다. 만일 jQuery만을 통해 웹앱을 개발한다고 생각하면 HTML 페이지마다 들어가는 서버코드와 자바스크립트들의 복잡한 코드에 머리를 쥐어 짤지도 모릅니다. 

  저는 13년을 넘게 자바언어로 서버만을 개발했었습니다. 가끔 클라이언트단의 jQuery 코드를 볼 때마다 저 영역으로는 절대 들어가지 말자라고 생각했습니다. 왜일까요? 그것은 서버처럼 잘 정비된 애플리케이션 프레임워크도 없으며, 서버코드와 자바스크립트를 HTML 사이사이에 끼워 넣으며 스파게티같은 코드를 짜야 하기 때문이었습니다. 

  그러나 2010년 이후부터 상황은 바뀌었습니다. Backbone.js가 이러한 복잡함을 해결하고자 초기 프레임워크로 나왔고, 이후 Ember.js 그리고 Angular.js 에서 Meteor.js 까지 모던 웹앱을 개발할 수 있는 프레임워크가 나왔습니다. 앵귤러는 어떻길래 이들 프레임워크 중 단연 선풍적인 인기를 누리고 있는 걸까요?


  - .js F/W  트랜드 



- .js 에서 dot(점)을 빼고 Backbonejs 와 Angularjs 트랜드 


  Backbonejs 와 Angularjs(노란색) 의 트랜드 변화는 급상승 중



2. Angular.js 가 어떻길래 개발자들이 열광하는가?

  앵귤러의 아버지인 미스코님(Misko Hevery)의 소개 동영상을 잠시 감상해 봅시다 


  요즘 SI시에 서버개발을 위하여 Spring Framework와 iBatis(myBatis)를 사용하는 것이 기본적인 관례처럼 되었는데요. 이들의 기능을 잠깐 생각해 볼까요. 

  - DI (Dependency Injection) 을 통하여 코드간의 결합도를 줄여주고, 테스트 코드의 작성을 쉽게 해줍니다. 

  - MVC 패턴를 통하여 기본적인 서버 개발의 틀을 가이드 해줍니다.

  - 다양한 라이브러리와 툴의 결합으로 개발 생산성을 높여줍니다.


 이를 앵귤러 입장에서 생각해 보면 정확히 위와 같은 요구사항을 충족해 주고 있습니다. 

  - DI를 지원합니다. 모듈단위의 개발로 코드를 간결하게 유지하고 테스트를 쉽게 해줍니다.

  - MV* 패턴을 통하여 역할을 나누고 아키텍쳐 Layered 개발을 가능하게 해줍니다. 당연히 유지 보수가 쉬워지겠죠

  - jQuery의 Plugins 포함한 기존의 다양한 라이브러리를 재 사용할 수 있습니다. 

  - 서버의 Maven, Ant 와 같은 관련된 라이브러리 의존성 관리 및 빌드 자동화 툴과 결합하여 개발 생산성을 높일 수 있습니다. 


  하지만 앵귤러를 시작하기 전에 하나의 선입견을 버리고 새로운 인식으로 접근을 해야 합니다. 자바스크립트는 이제 브라우져에서 화면의 단순 조작을 통한 효과에 쓰이는 언어가 아닌 진정한 엔터프라이즈급 애플리케이션부터 모바일 웹앱까지 모던한 애플리케이션을 만드는데 가장 많이 쓰이고 있는 개발언어가 되었다는 것입니다. 구글은 크롬앱을 코르도바(폰갭)를 통하여 안드로이드 및 iOS에서 구동하는 툴킷을 2014년 초에 출시할 계획입니다. 크롬앱은 자바스크립트로 개발합니다. 이는 본격적으로 네이티브 모바일 앱과 자바스크립트 모바일 웹앱이 함께 공존할 수 있는 시기가 왔다는 것을 암시합니다. 


 앵귤러(Angular.js)는 화면을 조작하는 라이브러리나 화면을 조작하는 프레임워크가 아니 모던 애플리케이션을 개발하는 프론트엔드 프레임워크입니다. 최근에는 이를 SPA(Single Page Application) 개발이라 부릅니다. Adobe의 Flex 기술을 통하여 클라이언트단에 엔터프라이즈 애플리케이션을 개발하는 RIA(Rich Internet Application) 가 선풍적인 인기를 누렸음을 잘 알것입니다. 인터넷이 되는 스마트 기기 에서 Adobe Flash의 공식 미지원 발표에 있은 후 이제 RIA의 용어는 점점 잊혀져 가고 있습니다. 하지만 사람들의 요구는 UX에 점점 더 목말라하고 있습니다. 단순한 웹서비스로는 이에 대응하기 힘들며 PC시대의 RIA를 표방한 Adobe의 Flex 프레임워크처럼 현재의 PC와 Mobile을 통합하는 진정한 RIA와 같은 자바스크립트 진영의 개발 프레임워크가 바로 앵귤러(Angular.js) 입니다. 무엇을 제공하 길래 앵귤러는 SPA 프레임워크의 큰형님이 되고 있는 걸까요?


 SPA vs RIA 비교 



3. Angular.js에는 무엇이 있는가?

  앵귤러는 클라이언트의 애플리케이션을 견고하게 만들 수 있는 방법을 제시하고 있습니다. 클라이언트의 생명은 바로 UX에 있다고 생각합니다. 훌륭한 사용자 경험을 주기 위하여 앵귤러 사용한다면 빠르게 개발하고 사용자의 피드백을 받아 개선해 갈 수 있습니다.


  견고한 싱글 페이지 웹 애플리케이션(SPA)을 만들기 위하여 앵귤러는 다음과 같은 기능을 제공합니다. 

  - 모듈 단위 개발을 통하여 글로벌 영역을 오염시키지 않고 모듈 단위 개발을 가능하게 해줍니다. 따라서 대규모의 애플리케이션 확장이 가능해 집니다. 


  - 양방향 데이터 바인딩을 통하여 View(HTML) 와 Controller(자바스크립트) 사이에 데이터에 대한 양방향 동기화를 자동으로 해줍니다. 즉, View 에서 데이터를 입력하면 Controller 단의 데이터를 업데이트 해주고, 그 반대도 가능해 집니다. 기존 jQuery에서는 데이터의 동기화를 위하여 Event와 Listener를 등록하는 코드를 모두 개발해야 했다면 앵귤러에서는 이런 코드를 찾아 볼 수 없습니다. Flex의 [Bindable]을 생각하면 됩니다. 


  - 앵귤러를 보통 MVW 프레임워크라 부릅니다. W는 Whatever의 의미로 Controller, Service등 무엇이든 올 수 있다는 것입니다. 역시 가장 중요한 것은 Model과 View의 연동을 양방향으로 함으로써 UX를 보다 쉽고 빠르게 개발토록 하는데 있습니다. 


  - Controller는 View HTML에서 발생한 이벤트에 따라 Model 변경을 제어하고, Service는 백앤드 서비스를 데이터 I/O 통신을 담당합니다. 각 필요한 기능은 DI(Dependency Injection)을 통하여 펑션의 파라미터 주입방식으로 인스턴스를 받아서 사용합니다. 


  - SPA 의 Single Page라는 의미는 최초에 index.html 을 서버로부터 받은 후 부분적인 화면의 변경을 위하여 부분 HTML(Partial HTML)만을 받아서 index.html의 특정 영역의 DOM 객체 변경을 통해 View의 일부분을 바꿔줍니다. 이후 서버로 부터 받는 것은 View의 컨텐츠 데이터인 JSON 이나 XML 입니다. 이렇게 화면의 부분 변경을 위하여 Routing 기능을 제공합니다. 


  - jQuery + PHP, jQuery + JSP 코드가 들어간 HTML 이 기존의 코딩 방식이라면 Javascript + HTML 만 존재하는 것이 앵귤러의 코딩 방식이다. 또한 반복해서 사용하는 HTML 또는 자바스크립트 코드조각이나 위젯/플러그인을 앵귤러만의 컴포넌트로 만들어서 HTML tag, attribute, class, comment 등의 방식으로 HTML에 포함시킬 수 있는 Directives(지시자) 기능을 제공합니다. Flex의 MXML에 차트관련 컴포넌트를 <Line> 태그로 표현하듯이, Angular.js의 Directive(컴포넌트)를 만들면 HTML안에 <Line> 과 같은 태그를 포함시킬 수 있습니다. 즉, Directive는 HTML 태그로 표기할 수 있는 컴포넌트 모듈을 만들 수 있고 Callback 펑션 기능을 내부로 숨기고 재사용할 수 있는 것이다. 


  - 백앤드로 Node.js를 사용한다면 Flex의 Messaging 기술과 같은 Push 방식의 구현을 Socket.io 모듈을 통하여 보다 더 쉽게 구현 할 수 있다. 


이제 자바스크립트 코딩이 가능하다면 싱글 페이지 애플리케이션(SPA) 에 도전해 봅시다.



<참조>

  - 스타트업을 위한 서비스 개발 기술 - MEAN Stack

  - 웹 애플리케이션 견고하게 만들기

  - 프론트 엔드 웹앱 프레임워크 (SlideShare)

posted by Peter Note
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 Note
2013. 11. 25. 09:36 AngularJS/Start MEAN Stack

MEAN Stack을 가지고 서비스를 만드는데 사용을 하려면 어떤 순서로 익혀야 할까? 각각의 기술을 따로 배우는 것은 어차피 전문서적들이 있기 때문에 여기서 다시 상세히 설명하는 것은 의미가 없을 것 같고, 서비스를 기획, 개발, 런칭까지의 과정을 알아보자. 모든 과정의 내용은 웹 서비스쪽에 공개되어 공유될 것이다 




Sprint-1) Full Stack 개발자 되기

  - MEAN 개념과 개요

    + AngularJS

    + NodeJS + ExpressJS

    + MongoDB + Redis(Advanced)

  - MEAN 이외에 알아야 할 것들

    + JavaScript 

    + Bootstrap (or Zurb Foundation

    + NodeJS Modules

    + Mongoose



Sprint-1) 개발 준비 단계

  - 로컬 개발 환경 만들기 

    + Vagrant + Linux 환경 구성하기

    + 개발환경에 Node.js, MongoDB 설치하기 

    + Trello 에서 Scrum 개발하기 

  - MEAN Stack 개발도구 개발환경에 설치하고 익히기 

    + Yeoman : 클라이언트 라이브러리관리 및 배포 Grunt, Bower, Yo 설치하기 

    + NPM : 서버 라이브러리 관리

  - Git & GitHub 저장소 만들기 

    + Git Branch 전략 

    + GitHub 사용방법

  - 클라우드 환경에 배포하여 테스트 하기

    + Cloud Development(Nitrous.io) 를 통한 협업개발 및 테스트하기

    + Heroku같은 PaaS에 배포하여 테스트하기 

 


Sprint-1) MEANK Stack 개발 환경 구성하기

  - SRS 작성하기 

    + Software Request Requirement란 무엇인가?

    + Trello 에 소프트웨어 요구사항 정의서 작성하기 

    + Balsamiq 도구 사용하여 Wireframe & Mockup 그리기 화면 그리기

  - AngularJS Scaffolding 코드 사용하기 

    + angular-generator 알아보기 

    + angular 기본골격 만들기

    + NodeJS로 테스트 하기 

  - MEAN Stack 테스트 전략 

    + AngularJS에서의 테스트 방법 : Karma Framework

    + NodeJS에서의 테스트 방법 : Mocha 그외 



Sprint-2) 서비스 프로토타입 개발하기

  - 가장 중요한 화면 설계하기

    + 클라이언트 화면 Mockup 구체화 하기 

    + NodeJs에서 처리 할 RESTful API 설계하기 

    + MongoDB 에 저장 할 Document Schema 설계하기

  - MongoDB Store 개발 및 테스트

    + Dynamic Schema, Embedded vs Reference 에 대한 이해 

    + Mongoose 기반으로 Schema 만들기

    + Test Framework 이용한 Model 단위 테스트하기 

  - Node.js 기반 RESTful API 개발 및 테스트

    + RESTful API 코드 뼈대 만들기 in NodeJS

    + Test Framework 이용한 서비스 단위 테스트하기

    + RESTful Client 테스트 도구 또는 curl 로 호출 테스트하기

  - Angular.js 기반 화면 개발 및 테스트

    + AngularJS와 Bootstrap 이용하여 화면 구성하기

    + AngularJS 애플리케이션 구조 개발 및 RESTful API 서비스 개발 

    + Test Framework 이용한 화면 단위 테스트하기 

  - Heroku 같은 PaaS에 올려서 통합 테스트 하기 



Sprint-3) 서비스 개발 반복하기

  - 반복하기 

    + sprint-01에서 반복하였던 과정을 지속하기 

  - 메인 및 로그인 화면 만들기 

    + Bootstrap의 다양한 도구 활용

    + OAuth 기능 로그인 하기  

  - 채팅기능 만들기 

    + 채팅 Mockup 만들기

    + NodeJS Socket.io API 구현

    + MongoDB 채팅 Schema 설계

    + AngularJS Socket.io 멀티 케스팅 구현 

    + Bootstrap 통한 화면 구현 및 AngularJS 연동

 - Heroku 같은 PaaS에 올려서 프로토타입 테스트 하기 



Sprint-3) 서비스 런칭하기

  - 서비스 일일 빌드시스템 구성하기 

    + Travis (또는 Jenkins)기반 자동 배포 기능 만들기 

    + Travis 에서 빌드된 것을 클라우드시스템(PaaS) 자동 배포하기 

  - 런칭 페이지 만들기 

    + GitHub Pages를 이용한 런칭 만드는 방법

    + Bootstrap을 이용하여 런칭 페이지만들어 GitHub에 반영하기 

  - 모니터링 하기

    + 클라이언트 AngularJS Log 모니터링 하기
    + NodeJS Log 모니터링 하기 

    + MEAN Stack 운영 및 장애 모니터링 전략 



Next Project) MEAN Stack Advanced

  - 채팅기능 고도화하기 

    + SNS 서비스 아키텍쳐들 알아보기  

    + 대용량 데이터를 처리를 위한 아키텍쳐 설계하기

  - NodeJS + Redis + MongoDB 연동하기 

    + Redis 알아보기 

    + NodeJS-Redis 연동하기 

  - 정제된 데이터 RDBMS와 연동하기

    + NodeJS - MySQL 연동하기 

    + MongoDB의 MapReduce를 이용하여 데이터 마이닝하기 

    + NodeJS에서 MongoDB 마이닝 데이터 MySQL에 넣기 

  - SNS 서비스 운영 고도화

    + NodeJS와 Nginx 연동하기 

    + NodeJS를 Proxy 서버로 만들어 Clustering 하기

    + MongoDB Sharding 구조 만들기 

    + Redis Master/Slave구조 만들기 

  - 유지보수 전략

    + 테스트 환경과 프로덕션 환경의 재구성

    + NodeJS Scale-out 확장 전략

    + MongoDB Sharding 및 Data Backup 전략 

    + Redis Scale-out 전략 

  - 빅데이터의 활용

    + 개인화를 위한 데이터 활용 방안

    + MongoDB Integration Framework을 이용한 MapReducing


전체 서비스를 만들고 런칭하기까지 참 많은 것들을 익히고 개발해야 하지만, 누군가에게 의미있는 가치를 줄 수 있는 SNS(Social Network Service)를 할 수 있다는 것에 만족을 느낀다. 


posted by Peter Note
prev 1 2 next