블로그 이미지
윤영식
Full Stacker, 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 윤영식
2015. 7. 11. 19:45 Meteor

2013년 이맘때 미티어를 처음 접하고 클라이언트와 서버 사이에 데이터베이스가 있다는 것이 이해되지 않았다. 그 의문이 이번주 스마트링크의 스터디를 하며 조금씩 해소되는 느낌이다. 이제는 Modern Application Platform의 큰 흐름에서 Runtime이라는 주제를 놓고 볼 때 미티어는 단연 앞선 플랫폼이고 한 분야를 리딩할만한 플랫폼이라 여겨진다. 그속에 많은 사상과 기술이 녹아있다. 미티어를 한주 안에 파악해 보기 위해 다음의 과정을 밟아보길 권한다. 






Meteor 넌 뭐니?


  - 스터디에서 발표한 내용의 동영상이다. 

    + Realtime을 근간으로 Collaboration과 Share를 위해 잘 갖추어진 Modern Application Platform 이다. 

    + Reactive 영역을 프론트엔드에서 백앤드로 확장해 기술셋을 잘 구비해 놓았다.

    + 프론트앤드 리액트 템플릿엔진 : Blaze, AngularJS, ReactJS를 사용할 수 있다. 

    + 백앤드 리액트 기술 : Full Stack DB Driver = DDP + Client mini DataBase + LiveQuery 를 갖추었다. 

    
    + 동영상의 발표자료
    


  - 위의 내용을 이해하기 위하여 다음과 같은 자료를 보았다. 

    + DiscoverMeteor 강좌 : 반드시 전과장을 직접 따라서 코딩해 보면 좋다. 물론 한글로 번역이 되어 있다. 

       이번에 출간한 "실전 프로젝트로 배우는 AngularJS"의 상당 부분이 이와 유사하게 전개한다. 놀란 것은 Node.js에서 MongoDB 제어와 권한 체크를 위한 코드 작성이 너무나도 쉽게 해결된다는 것이다. 정말 말 그대로 서버는 날로 먹는 것이다. Isomorphic Programming의 정수이다. 

    + 박승현님 Meteor Dev Study : 페북 미티어 코리아 그룹을 이끄시는 박승현님의 미티어 강좌를 보고 친숙하게 이해할 수 있을 것이다. 





Meteor Environment


미티어는 Ionic과 같은 CLI 제공을 통해 실행과 빌드를 통합하고 있다. 따라서 의존성관리를 위한 npm, bower 또는 빌드를 위한 grunt, gulp 명령등을 알 필요가 없다. 단순히 meteor <command> <option> 을 통해 완료된다. 


  - Meteor Command Line Tool 명령들

  - 미티어의 패키지 저장소 : Atomsphere

  - 많이 사용하는 패키지 검색 : Fastosphere





Meteor가 AngularJS 또는 ReactJS를 만났을 때

  

                    



  Blaze를 사용하지 않고 AngularJS를 사용할 때의 방식은 확연히 차이가 보인다. 만일 JQuery 개발 방식에 익숙하다면 Blaze를 사용한다. AngularJS를 사용해 보았다면 당장 AngularJS+Meteor를 사용하길 권한다. 공식적으로 지원을 하고 예제 문서까지 친절하게 나와있다. ReactJS는 아직 Preview단계로 보여지지만 긱하게 새로운 것을 도전해 보고 싶다면 권한다. ReactJS의 Virtual DOM 개념도 관심의 대상이지만 Flux라는 개념이 참 맘에 든다. 따라서 MVC 패턴이 아닌 좀 더 견고한(페북이 말하는) 패턴이라는 Flux 패턴을 적용하고 싶다면 ReactJS를 사용해 보자. 나의 선택은 물론 ReactJS + Flux 패턴이다. 


  - Blaze, AngularJS, ReactJS 비교하며 따라하기 예제

  - AngularJS + Meteor 홈페이지 : 물론 미티어 홈페이지에 AngularJS 공식 문서도 있다.'

  - ng vegas에서 발표한 angular-meteor 개발자인 유리 골드스테인의 Angular/Angular2와 Meteor에 대한 소개

    


  - ReactJS + Meteor Preview 예제 페이지

  - 페북과 유사한 ReactJS + Meteor 샘플 예제 : 데모 사이트를 이동해 직접 사용해 보자. 

    + Data Fetching Component는 Flux의 Controller View 이다.

    + Mini-Mongo Store는 Flux의 Store 이다.

    + Action은 Flux의 Action Creator 이다.

    



Meteor는 Modern Application Platform 이면서 클라이언트의 리액트를 백앤드로 확장해 주는 Reactive Platform이라 말할 수 있다. 다시 들여다 본지 1주일밖에 되지 않기에 오류를 잡고 파보아야할 곳이 많이 존재한다. 앞으로 두달간 진행하는 파일럿 프로젝트에서 ReactJS + Meteor를 사용하면서 내공을 쌓아야 할 상황이다. 




Modern Application Platform 으로서 Meteor 

  

  "Realtime 애플리케이션을 위해 프론트/백앤드 개발을 동질로 리액트하게 할 수 있는 플랫폼을 제공해 보자" 이게 그들의 머릿속을 스친 생각이 아니었을까? 리액티브 프로그래밍을 이소모픽하게 제공한다면 View 와 Data에 대하 부분을 리액티브하게 처리할 수 있다면... 아마 미티어 개발자들은 신이 나지 않았을까 싶다. 그래서 그와 관련된 회사들을 M&A하고 개발자를 영입해서 오늘의 1.* 미티어라는 리액티브 플랫폼을 완성해 가고 있다고 본다. 미티어를 리얼타임 플랫폼을 하는 마이크로 서비스로 보고 외부 시스템과 연계는 몽고디비를 통해 한다면 다양한 연동이 가능할 것이라 생각한다. 


  이중 DDP를 통한 React Native로 DDP Client를 사용하는 방법이 있는가 하면 

  

  일라스틱서치(Elastic Search)와 연동하기도 하고 

  

  

 미티어를 통해 상상하던 모던 애플리케이션 서비스를 일관되고 쉽게 만들어 갈 수 있다라는게 나의 결론이다. 




<참조> 


  - JavaScript.isSexy에서 이야기하는 Meteor 배우는 방법

posted by 윤영식
2015. 7. 4. 22:41 React

Flux는 페이스북이 ReactJS를 보다 더 큰 애플리케이션에 적용하기 위해 만든 아키텍처이다. 그러기 때문에 페이스북 웹 서비스와 인스타그램에도 적용되어 사용되고 있다. 하지만 처음 접하는 개발자에게는 생소한 패턴이고 새로운 용어들이 난무한다. Flux를 제대로 이해하기 위해서는 먼저 ReactJS의 데이터 흐름을 이해한 후 Flux를 보면 도움이 된다.  





Flux 넌 뭐니?


스터디에서 발표한 Flux 패턴에 대한 설명 동영상을 보자. 1시간 가량의 발표 및 토론 내용이다. 1주일동안 짬을 내어 Flux 예제 소스를 분석해 보았고, Flux 내의 Action, Dispatcher, Store, View 간의 데이터 흐름과 각각의 역할에 대해 살펴보고 있다. Flux를 이해하고 싶다면 본 예제를 돌려보고 설명하는 흐름에 따라 개념을 잡아보자. 

  - CartApp 예제에 대한 원본 글

  - CartApp 데모


   


발표한 내용의 PPT 자료도 동영상을 보며 살펴보자 

   - Flux 아키텍처에 대한 원본 글 


    




AngularJS에서 Flux 아키텍처 적용하기 


Flux는 아키텍처이고 사상일 뿐이다. 따라서 AngularJS에서도 당연히 적용해 볼 수 있다. 

  - AngularJS에서 Flux 적용하기 예제




참조 

  - Cart 소스 

  - ReactJS 개념 및 에코 시스템 이해하기 (스마트 링크 팀블로깅)

  - AngularJS vs ReatJS 이해하기

  - Flux 패턴 구현체들 (Fluxxor)

  - Flux pattern 설명

  - Flux를 이용한 Note App 예제

  - SlideShare: ReactJS와 Flux에서 데이터 흐름 설명 

'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[React] AngularJS와 ReactJS 비교  (0) 2015.07.01
[React] 배우는 방법  (0) 2015.05.15
posted by 윤영식
2015. 7. 1. 09:17 React
AngularJS로 여러번의 프로젝트를 했었다. 그럴때 마다는 느끼는 보다낳은 컨트롤러의 구조는 무엇일까? 그리고 $scope를 어떻게 하면 잘 사용할 수 있을까이다. 어쩌면 $scope에 대해 고민하기 보다 다른 접근법을 시도해 보는 것은 어떨까? 그래서 ReactJS를 공부하기 시작했다. 1주일밖에 안된 입장에서 어떻게 접근하는 것이 좋을지 간략히 정리하고 스마트링커들과 토론한 내용을 올린다. 



AngularJS와 ReactJS 비교

  - AngularJS와 ReactJS간의 개발 방법론에 대한 스터디 자료 

    



  - MVC 패턴과 ReactJS의 차이점을 이야기한다. 

   + 해당 발표를 보면 위에서 내가 설명한 내용을 좀 더 이해하기 쉽지 않을까 싶다.

   


'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[Flux] Flux 배우는 방법  (0) 2015.07.04
[React] 배우는 방법  (0) 2015.05.15
posted by 윤영식
2015. 1. 21. 16:15 My Projects/Jandi

2주차 진행을 하며 기존 웹 저장소를 별도로 구성해 완전히 별도의 애플리케이션을 구축할 예정이었으나 프러덕션 저장소의 업무로직의 복잡도와 지속적인 개발로 인한 신규 저장소 소스의 불일치 문제가 발생하였다. 따라서 완전히 새로운 저장소로 별도 애플리케이션을 개발하기 보다 Lagacy 코드를 리팩토링 하고 테스트 코드를 신규로 넣고 동시에 연속적인 빌드 환경을 구축하기로 한다. 



1. 모듈 리팩토링


  - AngularJS에서 하나로 묶여 있는 모듈을 여러개로 쪼개는 작업을 진행한다. 

    + 다행히 기존 애플리케이션이 generator-angular-fullstack 으로 되어 있기 때문에 별도로 작업했던 내용을 적용한다. 

    + jandiApp 모듈만 존재한다면 애플리케이션을 위한 추상화 모듈과 공통 모듈로 두가지를 생성한다. 

  - web_client 폴더는 jandi의 웹 애플리케이션 저장소 이름이다. 

    + web_client/client 폴더 밑으로 components 폴더를 만들고 그 밑으로 app와 base 폴더를 만든다. 

    + components/app는 애플리케이션과 연관이 있는 대표 모듈 폴더이다. 

    + components/base는 jandi외에도 어디서나 사용 가능한 그리고 웹에서 bower로 다운로드한 모듈의 폴더이다. 

$ cd web_client/client/

$ mkdir components && cd components

$ mkdir app

$ mkdir base


  - app와 base에 대한 대표 모듈을 만든다.

    + components/app/app.framework.js 파일 생성

    + components/base/base.farmework.js 파일 생성

    + app.framework 모듈은 base.framework 모듈을 의존한다. 

// app.framework.js 내역 

(function() {

  'use strict';


  angular

    .module('app.framework', ['base.framework']);

    

})();


// base.framework.js 내역 

(function() {

  'use strict';


  angular

    .module('base.framework', []);

    

})();


  - 자바스크립트 로딩 우선순위를 지정한다. 

    + index.html 파일의 app.js 보다 먼저 로딩 될 수 있도록 app.framework.js와 base.framework.js를 지정한다. 

    + grunt serve 명령 수행시 app.framework.js와 base.framework.js 파일이 자동 inject 되지 않도록 ingore 설정을 한다. 

    + watch와 inject에 경로를 설정하고 설정앞에 ! 주면 exclude하겠다는 의미이다. 

// index.html 일부 내역 

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

<script src="components/base/base.framework.js"></script>

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

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

<!-- injector:js -->

<script src="assets/javascripts/angulartics-ga-custom.js"></script>



// Gruntfile.js 일부 내역

 watch: {

            injectJS: {

                files: [

                    '<%= yeoman.client %>/{app,components}/**/*.js',

                    '!<%= yeoman.client %>/{app,components}/**/*.spec.js',

                    '!<%= yeoman.client %>/{app,components}/**/*.mock.js',

                    '!<%= yeoman.client %>/components/base/base.framework.js',

                    '!<%= yeoman.client %>/components/app/app.framework.js',

                    '!<%= yeoman.client %>/app/app.js'],

                tasks: ['injector:scripts']

            }, 

... 중략 ....

injector: {

            options: {},

            // Inject application script files into index.html (doesn't include bower)

            scripts: {

                options: {

                    transform: function(filePath) {

                        filePath = filePath.replace('/client/', '');

                        filePath = filePath.replace('/.tmp/', '');

                        return '<script src="' + filePath + '"></script>';

                    },

                    starttag: '<!-- injector:js -->',

                    endtag: '<!-- endinjector -->'

                },

                files: {

                    '<%= yeoman.client %>/index.html': [

                        [

                            '{.tmp,<%= yeoman.client %>}/assets/javascripts/*.js',

                            '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js',

                            '!{.tmp,<%= yeoman.client %>}/components/base/base.framework.js',

                            '!{.tmp,<%= yeoman.client %>}/components/app/app.framework.js',

                            '!{.tmp,<%= yeoman.client %>}/app/app.js',

                            '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.spec.js',

                            '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js']

                    ]

                }

            },


  모듈 리팩토링 준비가 되었다. 리팩토링할 대상을 선정한다. generator-angular-fullstack으로 애플리케이션을 생성하면 client/app/app.js 파일이 메인 파일이 된다. 먼저 app.js안에 애플리케이션 모듈 set과 config, run에 다양한 설정이 되어 있다면 이들을 base.framework 또는 app.framework으로 분리할 수 있는지 점검한다. 일반적으로 다음과 같은 상태로 app.js를 설정하고 한다. 





  위와 같이 app.js 한 곳에 다양한 설정들이 있는 것을 리팩토링을 통해 모듈단위로 나눌것이다. 이렇게 할 수 있는 것은 메인 모듈이 의존하는 모듈을 설정하면서 의존모듈을 로딩하면서 의존모듈의 config, run을 수행해 주고 그리고 자신의 모듈을 로딩하기 때문이다. app.js안에 app.framework과 base.framework 모듈의 의존관계를 설정하고 로그를 찍어보자. 먼저 의존관계의 모듈 config와 최종 애플리케이션 모듈인 jandiApp config를 로딩한 후 run을 다시 의존 모듈부터 수행시킨다. 즉, 애플리케이션을 최초 호출할 때 config, run 은 최초 한번 수행됨을 이용해서 각 기능의 초기화를 모듈단위로 나누는 것이다. 

// app.js 의존관계 설정 

angular.module('jandiApp', [

    'base.framework',

    'app.framework',

    ... 중략 ...]);


// config, run의 수행 순서 로그 

=========> Base framework Module config

=========> App framework Module config

=========> jandiApp Module config

=========> Base framework Module run

=========> App framework Module run

=========> jandiApp Module run




2. Config 모듈 생성 


 최초 환경 설정 부분을 모듈로 만들어서 app.framework 모듈에 의존성을 설정해 보자. app.js의 run 메소드에는 api 버전과 서버주소에 대한 기본 환경 설정이 있다. 이것을 config.js라는 별도의 모듈로 빼서 환경설정에 대한 부분을 독립시킨다. 

  - config.js 모듈 파일 생성 : 최초 환경 셋업이 필요한 부분은 config 모듈의 run 메소드에 설정한다. 즉, app.js 있던것을 옮겨옴 

  - config.service.js 서비스 생성 : 해당 서비스를 통해 어디서나 주입을 받아 사용토록 한다. 

$ cd web_client/components/app

$ mkdir config && cd config


// app.js 일부내역 

app.run(function() {

$rootScope.server_address   = configuration.api_address + "inner-api/";

      $rootScope.server_uploaded  = configuration.api_address;

$rootScope.api_version      = configuration.api_version;

$rootScope.configuration    = configuration;  

});


// components/app/config/config.js 내역 

(function() {

  'use strict';


  angular

    .module('app.config', [])

    .constant('configuration', {

      name                : 'local',

      api_address         : 'http://www.jandi.com/',

      api_version         : '2',

      ga_token            : 'UA-222222-1',

      ga_token_global     : 'UA-1111111-1',

      mp_token            : 'abcdefghijklmn',

      base_url            : '.jandi.io',

      base_protocol       : 'http://',

      main_address        : 'http://www.jandi.com/main/#/',

      app_store_address   : 'xxxx',

      play_store_address  : 'yyyy'

    })

    .run(run);


  /* @ngInject */

  function run($rootScope, config) {

    // server address, server api version 

    config.init($rootScope);

  }


})();


// components/app/config/config.service.js 내역 

(function() {

  'use strict';


  angular

    .module('app.config')

    .service('config', config);

    

  /* @ngInject */

  function config(configuration) {

    var self = this;

    this.init = init; 


    function init($rootScope) {

      // TODO : maybe remove next factoring

      // compatibility for current version

      $rootScope.server_address   = configuration.api_address + "api/";

      $rootScope.server_uploaded  = configuration.api_address;

      $rootScope.api_version      = configuration.api_version;

      

      // configuration constant service       

      configuration.server_address  = configuration.api_address + "api/";

      configuration.server_uploaded = configuration.api_address;

      configuration.api_version     = configuration.api_version;


      // config service

      self.server_address  = configuration.api_address + "inner-api/";

      self.server_uploaded = configuration.api_address;

      self.api_version     = configuration.api_version; 

    }

  }

})();


  config.js 에서 app.config 모듈을 run 하면서 config.service.js 를 주입받아 최초에 init()을 하고 있다. 파라미터로 $rootScope를 전달하는데 이는 초기 app.js에서 환경값을 $rootScope에 설정해서 사용하기 때문이다. 향후에는 $rootScope를 통해 접근하는 것을 없애고 config 서비스 통해 필요한 정보를 접근토록 할 예정으로 1차로 기존 버전의 애플리케이션 수정없이 환경설정으로 뺄 수 있는 상태로 리팩토링한다. AngularJS에서 초기 설정 정보를 접근하기 위한 원칙을 다음과 같이 세우면 좋다. 


  - AngularJS의 서비스는 singleton object 이므로 초기 값을 변수로 가지고 있고 접근자 API를 노출한다. 

  - AngularJS 서비스에 $scope, $rootScope를 절대로 주입하지 않는다. 오직 controller와 module.run 에서만 주입을 받아 사용한다. 

  - 따라서 서비스는 서비스를 주입받아 공통정보에 접근해야 한다. 


원칙에 따라 하나씩 모듈로 쪼개고 app.js에는 업무적인 로직에만 집중할 수 있도록 만든다. 향후 components/app/* 모듈은 사용자가 사용하는 웹화면과 관리자가 사용하는 웹화면에 동시에 적용되는 애플리케이션과 관련된 공통 모듈이 들어갈 것이다. 현재까지 모듈의 의존관계 모습은 다음과 같이 app.js의 jandiApp 의존관계는 app.framework 밖에 없다. 하지만 서로 의존관계가 설정되어 있기 때문에 jandiApp 모듈의 AngularJS 모든 컴포넌트는 base.framework과 app.framework의 의존관계 설정 모듈의 컴포넌트를 주입받아 사용할 수 있게 된다. 

// app.js 의존관계 

angular

  .module('jandiApp', [

    'app.framework'

  ]);


// app.framework.js 의존관계

 angular

    .module('app.framework', [

        'base.framework',

        'app.config'

      ])

    .run(run)

    .config(config);


// base.framework.js 의존관계

  angular

    .module('base.framework', [

      'ui.router',

      'ui.bootstrap',

      'gettext'

      'ngCookies',

      'ngResource',

      'ngSanitize',

      'ngAnimate'

    ])


  좀 더 구조적인 관계의 모듈 설정을 통해 구조적인 애플리케이션을 개발하는 첫단추를 꽤었다. 


'My Projects > Jandi' 카테고리의 다른 글

[Jandi] 팀 커뮤니케이션 서비스  (0) 2015.01.07
posted by 윤영식
2015. 1. 7. 11:29 My Projects/Jandi

Tosslab의 잔디는 팀 그룹간에 원활한 커뮤니케이션을 위한 도구이다. 채팅 및 파일공유를 할 수 있어서 업무 커뮤케이션 툴로 사용하기에 적당하고 모바일, 웹에서 접근할 수 있다. 



잔디의 기술


  잔디는 MEAN Stack 기술을 사용하고 모바일은 네이티브로 개발한다. 업무적인 커뮤니케이션 도구는 사무실에서 웹을 통해 사용할 경우가 많아서 특히 웹에 대한 사용성이 좋아야 한다. 블로그에서는 프론트앤드 영역만을 다루도록 한다. 기존에는 AngularJS v1.2.* 기반으로 되어 있고 개발을 진행함에 따라 웹 애플리케이션의 복잡도가 증가하여서 애플리케이션을 재구성할 필요가 발생하였다. 




모듈단위 애플리케이션 구성


 애플리케이션 복잡도를 단순화 시키고 애플리케이션을 구조화하는 방안이 필요하다. 애플리케이션을 구조화 한다는 것은 거시적인 부분과 미시적인 부분의 조합으로 이루어진다. 먼저 거시적인 부분은 아키텍쳐레벨로 AngularJS의 모듈을 구성하는 것이다. 


 

  AngularJS 모듈은 컴포넌트의 그룹이다. 모듈안에는 다양한 컴포넌트를 가지고 있고 컴포넌트 성격별로 구분해 놓을 필요가 있다. 위의 예에서 가장 기본으로 있어야할 AngularJS에서 제공하는 모듈 묶음과 어느 프로젝트에서나 쓰일만한 모듈을 묶어놓은 공통 모듈이 있을 것이다. 그리고 업무적으로 추상화할 필요가 있는 업무 공통 모듈이 존재하고 업무를 처리하는 업무 모듈식으로 나눌 수 있다. 하위 모듈은 상위 모듈을 참조할 수 없고 상위 모듈만이 하위모듈안의 컴포넌트를 DI(Dependency Injection) 방식으로 사용을 할 수 있다. 




제너레이터를 통한 애플리케이션 구성


 기존의 애플리케이션을 보기하면서 다시 재구성하기 위해 무엇부터 시작을 해야할까? 


  - Yeoman Generator를 선정한다. generator-angular-fullstack을 사용할 예정이다. 

  - 자바스크립트 코딩 스타일은 구글의 자바스크립트 스타일 가이드를 따른다. 

  - AngularJS의 코딩 스타일은 존파파의 AngularJS 스타일 가이드를 따른다.


  generator-angular-fullstack의 템플릿을 존파파의 가이드에 따라 변경한 프로젝트를 만들어 본다. 불필요한 예제는 제거하고 몇가지 영역의 템플릿만을 취할 것이다. 먼저 generator-angular-fullstackgenerator-ng-component를 각각 git clone 하고 명칭을 generator-jdi, generator-jdi-component라고 바꾸고 내부의 .git폴더를 제거한 후 각 템플릿의 내용을 파파죤스 가이드에 따라서 수정을 한다. 

$ git clone https://github.com/DaftMonk/generator-angular-fullstack.git

$ git clone https://github.com/DaftMonk/generator-ng-component.git


$ mv generator-angular-fullstack generator-jdi

$ mv generator-ng-component generator-jdi-component


$ cd generator-jdi && rm -rf .git

$ cd generator-jdi-component && rm -rf .git


 파파죤스 가이드에 따라 generator-jdi-component/templates 폴더 밑의 모든 앵규러 컴포넌트를 다음과 같은 형식으로 수정한다. 

   + IIFE : Immediately Invoke Function Expression

   + 앵귤러 모듈 변수를 만들지 않음 

   + 컴포넌트의 펑션을 밖으로 뺌

   + DI 하는 부분은 ng-annotation을 이용함으로 주입받는 펑션위에 /* @ngInject */ 를 둚

(function() { 

  'use strict';


  angular

    .module('<%= scriptAppName %>')

    .controller('<%= classedName %>Ctrl', <%= classedName %>Ctrl);


  /* @ngInject */

  function <%= classedName %>Ctrl($scope) {

         

  }

})();


 수정한 내역에 대해 테스트는 다음과 같이 진행을 하면 된다.  먼저 테스트용 test 폴더를 하나 만든다. 테스트 폴더 밑에 node_modules 폴더를 만들고 밑으로 generator-jdi와 generator-jdi-component 에 대해 심볼릭 링크를 건다. 각 generator-* 폴더로 들어가 npm install을 해준다. 이제는 test밑에서 "yo jdi Jandi" 명령을 치면 애플리케이션 생성 프롬프트를 묻게 된다. 애플리케이션 템플릿은 generator-jdi/app/templates 밑에 존재하고 여기에 있는 파일도 파파죤스 스타일 가이드에 따라 수정을 해준다. 

$ mkdir test && cd test

$ mkdir node_modules && cd node_modules 


$ ln -s <fullPath>/generator-jdi  generator-jdi

$ ln -s <fullPath>/generator-jdi-component generator-jdi-component


// 둘은 같은 디렉토리에 있어야 함 

$ cd <fullPath>/generator-jdi-component && npm install

$ cd <fullPath>/generator-jdi && npm install 


$ cd test 

$ yo jdi Jandi


  각 수정한 generator-jdi, generator-jdi-component는 링크를 참조한다. 이제는 누구나 사용할 수 있도록 npm repository에 등록을 한다. npm publish 로 package.json 파일에 등록된 내역을 기반으로 배포가 된다. 따라서 clone한 generator의 버전을 따르지 않고 최초 0.0.1 버전으로 수정해 배포한다. 

$ cd generator-jdi-component 

$ npm publish

+ generator-jdi-component@0.0.1


$ cd generator-jdi

$ npm publish

+ generator-jdi@0.0.1


 이제 글로벌로 generator-jdi만을 설치해서 어디서나 "yo jdi <App명칭>" 명령을 수행해 최초 애플리케이션 골격을 만들 수 있다. 

$ npm install -g generator-jdi

$ mkdir web_tiger && cd web_tiger

$ yo jdi Jandi



'My Projects > Jandi' 카테고리의 다른 글

[Jandi] 애플리케이션 모듈 리팩토링  (0) 2015.01.21
posted by 윤영식
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 윤영식
2014. 12. 24. 17:15 Languages/Python

처음으로 새로운 프레임워크를 접하게 되었을 때 어떻게 시작해야할지 막막하다. 그럴땐 공부할 꺼리들을 정리해 보고 시작하자. 새롭게 시작할 AngularDart의 컨텐츠를 정리해 보자. 






AngularJS 개발자가 AngularDart 개발하기 


  - AngularDart의 상당 부분이 실은 AngularJS v2.0에 많은 영향을 미쳤다. (미스코의 말)

    + AngularJS개발자를 위한 AngularDart 이해하기

  - Dartlang을 모르고서 AngularDart를 보면 안되겠죠?  

  - UI Framework과 연동해서 웹 애플리케이션을 만들어 본다. (깃헙 소스)

    


  - 코드랩을 따라한다.

    + 예제 데모를 돌려보면 최신 IE 11, FF, Safari, Chrome에서 잘 돌아감. 

    + HTML의 디렉티브는 AngularJS와 거의 같음. 코드만 .dart 로 작성함 

  - 개발하며 API를 참조한다.





AngularDart와 Polymer 연동하기


  - 우선 AngularDart 소개영상과 25분부터 이어지는 Polymer.Dart를 보자 

    + 보고 있으면 아마도 ng-conf 2014에서 소개한 Angular v2.0 영상의 14분 부터 보면 뭔가 떠오를지 모르겠다. 

    

 

  - 첫번째 영상에서 Web Component 개념을 이야기하는 Polymer.dart를 다시 보자. 

  - 다시 정리하고 있는 Parsley의 Web Component & Dart 동영상을 보자

  - 따라하기 하며 Dart와 Polymer.dart를 연결해 보기 


계속해서 업데이트 예정...




<참조> 


  - Dart와 Web Component에 대한 다른 영상

  - 미스코 헤베리의 AngularDart를 만든 이유 

The Angular team's mission is to make building web-applications easy on the web. We want to support as many languages as possible.

AngularJS supports: JavaScript and through transpilation CoffeeScript and TypeScript.

AngularDart obviously supports Dart. 

1) Dart is significantly different from JavaScript that transpilation approach would not create the experience we were looking for, for this reason we have rewritten it. 

2) Dart has Types, and Type annotations, and so while rewriting AngularDart we have chose to take advantage of these language features. 

3) We have also learned from our mistakes on AngularJS and taken the opportunity to correct them, such as much improved Directive API. Finally we have take advantage of the latest browser technologies and based AngularDart on top of Shadow DOM.

4) All of the learnings we have gained in building AngularDart, will be applied back to AngularJS v2. This is only expected since AngularDart is younger than AngularJS.

Angular is a philosophy on how applications should be built and we wanted to make the question of language choice orthogonal to your choice of using Angular. Any web-language you choose, there is an Angular you can use with it.



'Languages > Python' 카테고리의 다른 글

[Dart] Dart 배우는 방법  (0) 2014.12.31
[Dart] 다트 언어 개념잡기  (0) 2014.12.24
posted by 윤영식

BroadLeaf eCommerce 솔루션은 서버는 자바 Spring Framework와 Hibernate를 사용하고 상품에 대한 검색은 Solr를 사용한다. 그리고 클라이언트는 ThymeLeaf 템플릿 엔진을 사용해서 BroadLeaf과 강하게 결합해서 사용된다. BroadLeaf 데모 사이트를 실제 사용할 수 있는 eCommerce 사이트로 전환하는 과정을 살펴본다. 또한 Frontend 쪽에 Twitter BootstrapAngularJS를 적용해 Tymeleaf과 상호 운용될 수 있도록 설정해 본다.






Broadleaf DemoSite 설치 


  - BroadleafCommerce 는 코어 소스이고 Community 버전으로 오픈소스를 제공한다. 

  - BroadleafCommerce 코어 소스를 사용한 DemoSite를 제공한다. 

  - DemoSite를 깃헙에서 받아 사용해도 되고 eclipse workspace 파일을 별도로 이곳에서 받아도 된다. (다운로드 버전은 3.1.8 버전)

    + 만일 3.1.9 버전을 사용하고 싶다면 깃헙의 DemoSite 릴리즈에서 3.1.9-GA를 다운로드하면 된다.


  - DemoSite를 maven project로 eclipse에 import하는 과정은 BroadLeaf Starting 가이드를 참조한다.

    + eclipse는 luna를 사용하면 eclipse용 maven 플러그인이 기본 설치되어 있다. 

    + 단, Apache Maven 은 다운로드해서 설치하고 path를 잡아준다.

    + 최초 DemoSite/build.xml 을 Ant view에서 열고 "change-identifier"를 수행하고

       maven의 grouId를 com.xxxx 식으로 넣고  "y"를 선택하면 자바 패키지와 maven groupId등을 설정한 값으로 자동 변경한다. 


  - 최초에 메이븐 빌드를 한번 해준다. 먼저 DemoSite로 이동해서 하기 명령을 한번 수행한다

$ cd DemoSite

$ mvn clean install


  - Broadleaf은 Site와 Admin으로 구분되어 Site는 쇼핑사이트를 Admin은 카랄로그와 컨텐츠를 관리하고 각각 8080, 8081를 사용한다.

    + site/build.xml 과 admin/build.xml 을 ant view에서 열고 "jetty-demo"를 수행하면 된다. 

    + 데이터베이스는 기본으로 메모리DB인 HSQLDB를 사용한다.

Site

http://localhost:8080


Admin (id: admin, password: admin)

http://localhost:8081/admin/




MySQL전환하기 


  - MySQL 전환작업은 가이드를 참조한다. 

  - mysql로 전환하기 위해 데이터베이스와 계정을 broadleaf 로 지정하고 계정 암호는 abc1234 로 생성한다.

-- %> mysql --user=root -p mysql

-- Enter password: ******


-- mysql> grant all on broadleaf.* to broadleaf@localhost identified by 'abc1234' with grant option;

-- mysql> create database broadleaf;

-- mysql> flush privileges;


-- %> mysql -u broadleaf -p broadleaf

-- Enter password: ******


-- mysql> show grants for broadleaf@localhost;

-- mysql> select current_user;


  - admin과 site가 mysql을 사용하도록 DemoSite/pom.xml과 admin/pom.xml과 site/pom.xml에 다음을 설정한다 

// DemoSite/pom.xml  안에 설정 

<dependency>

    <groupId>mysql</groupId>

    <artifactId>mysql-connector-java</artifactId>

    <version>5.1.34</version>

    <type>jar</type>

    <scope>compile</scope>

</dependency> 


// admin, site/pom.xml 안에 설정 

<dependency>

    <groupId>mysql</groupId>

    <artifactId>mysql-connector-java</artifactId>

</dependency> 


  - jetty를 통해 기동될 때 데이터베이스 커넥션 유형을 3가지를 사용하는데 HSQLDB에서 MySQL로 환경값을 바꾸어 주어야 한다. 

    + /site/src/main/webapp/WEB-INF/jetty-env.xml

    + /admin/src/main/webapp/WEB-INF/jetty-env.xml

<New id="webDS" class="org.eclipse.jetty.plus.jndi.Resource">

    <Arg>jdbc/web</Arg>

    <Arg>

        <New class="org.apache.commons.dbcp.BasicDataSource">

            <Set name="driverClassName">com.mysql.jdbc.Driver</Set>

            <Set name="url">jdbc:mysql://localhost:3306/broadleaf?useUnicode=true&amp;characterEncoding=utf8</Set>

            <Set name="username">broadleaf</Set>

            <Set name="password">abc1234</Set>

        </New>

    </Arg>

</New>

<New id="webSecureDS" class="org.eclipse.jetty.plus.jndi.Resource">

    <Arg>jdbc/secure</Arg>

    <Arg>

        <New class="org.apache.commons.dbcp.BasicDataSource">

            <Set name="driverClassName">com.mysql.jdbc.Driver</Set>

            <Set name="url">jdbc:mysql://localhost:3306/broadleaf?useUnicode=true&amp;characterEncoding=utf8</Set>

            <Set name="username">broadleaf</Set>

            <Set name="password">abc1234</Set>

        </New>

    </Arg>

</New>

<New id="webStorageDS" class="org.eclipse.jetty.plus.jndi.Resource">

    <Arg>jdbc/storage</Arg>

    <Arg>

        <New class="org.apache.commons.dbcp.BasicDataSource">

            <Set name="driverClassName">com.mysql.jdbc.Driver</Set>

            <Set name="url">jdbc:mysql://localhost:3306/broadleaf?useUnicode=true&amp;characterEncoding=utf8</Set>

            <Set name="username">broadleaf</Set>

            <Set name="password">abc1234</Set>

        </New>

    </Arg>

</New>


  - 하이버네이트의 Dialect를 HSQLDB에서 MySQL로 바꾼다. 

     + /core/src/main/resources/runtime-properties/common-shared.properties  의 맨 하단에 다음을 넣는다. 

# overriding dialect 

blPU.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

blSecurePU.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

blCMSStorage.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect


  - DemoSite의 build.properties의 기본 환경값을 HSQLDB에서 MySQL로 바꾼다. 

    + /DemoSite/build.properties 의 맨 하단에 다음을 넣는다. 

# new overriding 

ant.hibernate.sql.ddl.dialect=org.hibernate.dialect.MySQL5InnoDBDialect


ant.blPU.url=jdbc:mysql://localhost:3306/broadleaf

ant.blPU.userName=broadleaf

ant.blPU.password=abc1234

ant.blPU.driverClassName=com.mysql.jdbc.Driver


ant.blSecurePU.url=jdbc:mysql://localhost:3306/broadleaf

ant.blSecurePU.userName=broadleaf

ant.blSecurePU.password=abc1234

ant.blSecurePU.driverClassName=com.mysql.jdbc.Driver


ant.blCMSStorage.url=jdbc:mysql://localhost:3306/broadleaf

ant.blCMSStorage.userName=broadleaf

ant.blCMSStorage.password=abc1234


  - 그리고 가장 중요한 설정은 site와 admin 의 common.properties에 맨 하단에 다음 사항을 필히 넣어 주어야 BLC_SKU 테이블이 생성됨

    + /site/src/main/resources/runtime-properties/common.properties

    + /admin/src/main/resources/runtime-properties/common.properties

# overriding dialect 

blPU.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

blSecurePU.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

blCMSStorage.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect


  - 전환 후에는 ant task에서 "build-app"를 해주고 "jetty-run-no-db"를 선택하면 build한 target 디렉토리의 소스가 적용된다. 


     




참조 

  

  - Broadleaf v3.1 Guide

posted by 윤영식
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 윤영식
2014. 8. 4. 00:11 AngularJS/Concept

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



1. Directive 사용 영역

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

<angular></angular>

<div angular></div>

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

<!-- directive: angular -->



2. 이름 규칙

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

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

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


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



3. 테스트전 SaaS

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

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

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

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

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



4. 간단한 테스트 수행

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

  - 이름 : angular

// index.html

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

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

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

</html>


// script.js

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


  - plunker preview


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



5. Directive Definition

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

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

  - Directive 정의

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



5-1. Compile function

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

  - 2개 파라미터

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

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

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

// in index.html

<div compile-check></div>


// in script.js

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


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



5-2. Link function

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

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

  - 3개 파라미터

    + element : directive와 바인드된 요소

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

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

 


5-3. Restrict

  - E : elements

  - A : attributes

  - C : class name (CSS)

  - M : comments

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

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



5-4. Template

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

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

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

// index.html

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

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

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


// script.js

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

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


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



5-5. TemplateUrl

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

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

  - <script> 태그를 이용한다

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


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

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

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

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


  - script.js 내역 

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

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


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



5-6. Replace

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

  - replace: true

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

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

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

  </head>

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


  - script.js 내역

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

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


  - 결과 html

// attribute

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


// class name

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


// element

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


// comment

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


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



5-7. Priority

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

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

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

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

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

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

  </head>

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


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

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

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


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



5-8. Terminal

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

  - index.html 내역

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

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

  </head>

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


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

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

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

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


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



5-9. Controller

  - Directive를 제어를 담당한다.

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

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

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

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



5-10. Require

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

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

  - prefixed 부호가 붙는다

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

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


  - index.html 내역 

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

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

  </head>

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


  - script.js 내역

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

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


  - 변경내역 : toggle off일 경우

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


  - 결과 화면 : uppercase 필터 사용



5-11. Scope

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

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

  - scope: false

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

  - scope: true

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


  - index.html 내역

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

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

  </head>

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


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

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

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

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

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


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

 

  - scope: true

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


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


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



  - scope: false

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


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


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




  - scope: 'isolate'

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

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

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

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

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

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

 

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

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

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

  </head>

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

  </body>
 
</html>


  - script.js 내역

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

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

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

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


  - 결과화면

 

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



5-12. Transcude

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

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

// index.html

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

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

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



// script.js

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

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



// DOM 내역

<div whoiam="">

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

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

</div>


<div class="caption">

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

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

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

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

  <marquee ng-transclude="">

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

  </marquee>

 </div>


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


  - transclude: 'element'

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

// index.html

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

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


// script.js

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

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

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


// DOM 내역

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

    </div>

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

</body>


// 결과 화면



<참조>

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

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

  - Yeoman 사용하기

posted by 윤영식
2014. 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 윤영식
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 윤영식
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 윤영식
2014. 4. 25. 00:15 NodeJS/Concept

Node.js를 제대로 배워보고 싶다면 이렇게 시작해보자


1. Node.js 사용 영역

  - fast, real-time 어플리케이션 개발

  - scalable 확장가능성이 높은 어플리케이션 개발

  - data-driven modern web 어플리케이션 개발

  * 클라우드 환경 이해



2. 잘 못 된 코스 

  - 온라인 및 동영상 강좌들부터 보지 말기

  - 서점에서 서평들 읽고 책 고르지 말기 



3. 제대로 된 코스

  - 봐야할 리소스 

    + 초보 : The Node Beginner Book 번역본

    + Felix's NodeJS Guide 번역본

   + Node.js 소개 자료 -여름으로 가는 문

   + Mastering NodeJS (비젼미디어)


  - JavaScript 를 배운다 : JavaScript Graden 번역본


  - 설치 : Professional Node.js 1장 보고 개발환경 셋업

  - 이해 : Beginner Book 보고서 간단히 프로그램들 돌려보기 

  - 모듈 : CommonJS에 대하여 이해하기

  - Professional Node.js 3장부터 쭉 읽자. 그러면 서버단 개발에 대해서 이해할 수 있다

  - 우리 목적은 modern web application개발이니 front-end의 backbone.js 를 배운다 



4. Node.js & Backbone.js를 익혔다면 

  - http://dailyjs.com/web-app.html 여기서 다양한 어플리케이션의 모험을 해보시라

  - 더 나아가서는 템플릿을 위한 Handlebars.js 와 MongoDB를 익힌다


  * 2013.2.20 일에 작성한 블로그라서 backbone.js가 나왔다. 이제는 angular.js로 옮겨가는 분위기

    3주를 목표로 잡고 시작하시라~~~ Good Luck!



<참조>

  - 원문 : http://javascriptissexy.com/learn-node-js-completely-and-with-confidence/

posted by 윤영식
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 윤영식
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 윤영식
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 윤영식
2013. 10. 21. 19:01 Testing, TDD/Test First

Karma 테스트 프레임워크를 통하여 AngularJS기반 애플리케이션을 어떻게 테스트 하는지 알아보자 



AngularJS의 두가지 테스트 타입 

  - Unit Testing : 작고 격리된 코드 유닛의 동작에 대한 검증 테스트 

  - E2E Testing : 애플리케이션 전영역에 대한 테스트. E2E 테스터는 로봇처럼 브라우져를 실행하여 페이지를 검증해 준다 

  - AngularJS에서 이야기하는 E2E테스트 시나리오 API : Angular Scenario Runner 

    + describe 

    + beforeEach

    + afterEach

    + it

describe('Buzz Client', function() {

 it('should filter results', function() {

   input('user').enter('jacksparrow');

   element(':button').click();

   expect(repeater('ul li').count()).toEqual(10);

   input('filterText').enter('Bees');

   expect(repeater('ul li').count()).toEqual(1);

 });

});


  - Testing의 유형을 나누어 본다 : Unit, Midway, E2E

    + Unit Testing

      코드레벨 테스트

      격리된 테스트

      Mock & Stubbing 요구, 빠름

    + Midway Testing

      애플리케이션/코드레벨 테스트

      애플리케이션의 모든 부분 접근 가능

      다른 애플리케이션 코드와 상호작용

      뷰(html) 내부의 테스트는 불가능능

      빠름, 단, XHR일 경우 느릴 수 있음 

    + E2E Testing

      웹레벨 테스트

      웹서버가 필요

      완벽한 통합 테스트 



샘플코드 설치 및 수행

  - 데모 페이지

  - 소스 : GitHub Repository

  - 설치 (참조)

$ git clone git://github.com/yearofmoo-articles/AngularJS-Testing-Article.git

Cloning into 'AngularJS-Testing-Article'...

remote: Counting objects: 536, done.

remote: Compressing objects: 100% (368/368), done.

remote: Total 536 (delta 130), reused 516 (delta 117)

Receiving objects: 100% (536/536), 1.28 MiB | 390.00 KiB/s, done.

Resolving deltas: 100% (130/130), done.


$ cd AngularJS-Testing-Article/ && npm install


// 만일 제대로 npm install시 에러가 발생하면 package.json의 버전을 바꾼다

$ vi package.json

   "grunt-karma": "latest",

    "karma": "latest",

  - 수행 : http://localhost:8000 으로 호출한다 

$ grunt server

Running "connect:server" (connect) task

Starting connect web server on localhost:8000.

Waiting forever...

  - 디렉토리 구조 

    + test 밑에 3가지의 karma 환경파일이 존재한다

    + grunt test 명령으로 test 프로그램을 수행할 수 있다 - grunt test, grunt karma:unit, grunt karma:midway, grunt test:e2e (참조)

       * 테스트 안될 경우 주의

        1) bower_components를 components 폴더로 명칭 변경

        2) ngMidwayTester 못 찾으면 : comoponents/ngMidwayTest/src 에서 src를 Source 폴더로 명칭 변경  

    + 각 파트별 수행 테스트 파일은 unit, midway, e2e 폴더로 나뉜다 

    

  - Karma안에서 사용한 Test Frameworks은 test/ 폴더 밑으로 karma-e2e/midway/unit/shared.conf.js 로 설정되어 있다 

    + Mocha.js : Unit Test

    + Chai.js : matchers and assertions

    + Jasmine.js : End-To-End Test(E2E) Framework으로 Angular Scenario Runner에서 사용


  * Midway Plugin에서 에러가 발생한다. 해결하면 500원



Mocha, Chai 그리고 Angular Scenario Runner

  - Unit 과 Midway를 테스트에는 테스트 스팩은 Mocha(모카)를 이용하고 Matcher/Assertion으로 Chai를 사용한다  

  - E2E 테스트시에는 Mocah, Chai를 사용할 수 없었다. 별도의 E2E 수행 문서를 제공한다

  - 테스트 시에 AngularJS는 XHR 요청도 Interceptors를 통해서 가로챌 수 있다. 

     그외 route, controller, service, directive까지도 가로채서 테스트 코드를 넣을 수 있다. 

  - XHR 가로채기의 비용이 크면 angular-mock.js를 통하여 Mock을 사용할 수도 있다 



1. Testing Module

  - AngularJS에서 모듈은 directives, controllers, templates, services 그리고 resources 생성을 위한 컨테이너 오브젝트이다

  - 따라서 테스트에 최초로 할 것이 해당 모듈이 있는지 체크하는 것이다 

  - Midway 테스트에서 수행한다 

  - midway/appSpec.js

//
// test/midway/appSpec.js
//
describe("Midway: Testing Modules", function() {
  describe("App Module:", function() {

    var module;
    before(function() {
      module = angular.module("App");
    });

    it("should be registered", function() {
      expect(module).not.to.equal(null);
    });

    // App이 의존하는 모듈들이 있는지 검증 
    describe("Dependencies:", function() {

      var deps;
      var hasModule = function(m) {
        return deps.indexOf(m) >= 0;
      };
      before(function() {
        deps = module.value('appName').requires;
      });

      //you can also test the module's dependencies
      it("should have App.Controllers as a dependency", function() {
        expect(hasModule('App.Controllers')).to.equal(true);
      });

      it("should have App.Directives as a dependency", function() {
        expect(hasModule('App.Directives')).to.equal(true);
      });

      it("should have App.Filters as a dependency", function() {
        expect(hasModule('App.Filters')).to.equal(true);
      });

      it("should have App.Routes as a dependency", function() {
        expect(hasModule('App.Routes')).to.equal(true);
      });

      it("should have App.Services as a dependency", function() {
        expect(hasModule('App.Services')).to.equal(true);
      });
    });
  }); 

});



2. Testing Routes

  - 애플리케이션의 페이지 라우팅이 제대로 되는지 테스트 한다 

  - Midway, E2E 테스트에서 수행한다 

  - Midway Testing (Midway Testser Plugin)

//
// test/midway/routesSpec.js
//
describe("Testing Routes", function() {

  var test;

  before(function(done) {
    test = new ngMidwayTester();
    test.register('App', done);
  });

  it("should have a videos_path", function() {
    expect(ROUTER.routeDefined('videos_path')).to.equal(true);
    var url = ROUTER.routePath('videos_path');
    expect(url).to.equal('/videos');
  });

  it("the videos_path should goto the VideosCtrl controller", function() {
    var route = ROUTER.getRoute('videos_path');
    route.params.controller.should.equal('VideosCtrl');
  });

  it("the home_path should be the same as the videos_path", function() {
    expect(ROUTER.routeDefined('home_path')).to.equal(true);
    var url1 = ROUTER.routePath('home_path');
    var url2 = ROUTER.routePath('videos_path');
    expect(url1).to.equal(url2);
  });
 

});

  - E2E Testing 

// test/e2e/routesSpec.js
//
describe("E2E: Testing Routes", function() {

  beforeEach(function() {
    browser().navigateTo('/');
  });

  it('should jump to the /videos path when / is accessed', function() {
    browser().navigateTo('#/');
    expect(browser().location().path()).toBe("/videos");
  });

  it('should have a working /videos route', function() {
    browser().navigateTo('#/videos');
    expect(browser().location().path()).toBe("/videos");
  });

  it('should have a working /wathced-videos route', function() {
    browser().navigateTo('#/watched-videos');
    expect(browser().location().path()).toBe("/watched-videos");
  });

  it('should have a working /videos/ID route', function() {
    browser().navigateTo('#/videos/10');
    expect(browser().location().path()).toBe("/videos/10");
  });

});



3. Testing Controllers

  - $scope를 통하여 template 페이지와 데이터 바인딩 되는 테스트 

  - Unit Testing 

// test/unit/controllers/controllersSpec.js
//
describe("Unit: Testing Controllers", function() {

  beforeEach(angular.mock.module('App'));

  it('should have a VideosCtrl controller', function() {
    // controller 모듈이 있는지 검증
    expect(App.VideosCtrl).not.to.equal(null);
  });

  it('should have a VideoCtrl controller', function() {
    expect(App.VideoCtrl).not.to.equal(null);
  });

  it('should have a WatchedVideosCtrl controller', function() {
    expect(App.WatchedVideosCtrl).not.to.equal(null);
  });

  // $httpBackend Mock 객체를 통해 단위 테스트 검증
  it('should have a properly working VideosCtrl controller', 
    inject(function($rootScope, $controller, $httpBackend) {
    var searchTestAtr = 'cars';
    var response = $httpBackend.expectJSONP('https://gdata.youtube.com/feeds/api/videos?q='
                     + searchTestAtr + '&v=2&alt=json&callback=JSON_CALLBACK');
    response.respond(null);

    var $scope = $rootScope.$new();
    var ctrl = $controller('VideosCtrl', {
      $scope : $scope,
      $routeParams : {
        q : searchTestAtr
      }
    });
  }));

  it('should have a properly working VideoCtrl controller', 
       inject(function($rootScope, $controller, $httpBackend) {
    var searchID = 'cars';
    var response = $httpBackend.expectJSONP('https://gdata.youtube.com/feeds/api/videos/' 
                     + searchID + '?v=2&alt=json&callback=JSON_CALLBACK');
    response.respond(null);

    var $scope = $rootScope.$new();
    var ctrl = $controller('VideoCtrl', {
      $scope : $scope,
      $routeParams : {
        id : searchID
      }
    });
  }));

  it('should have a properly working WatchedVideosCtrl controller', 
         inject(function($rootScope, $controller, $httpBackend) {
    var $scope = $rootScope.$new();

    //we're stubbing the onReady event
    $scope.onReady = function() { };
    var ctrl = $controller('WatchedVideosCtrl', {
      $scope : $scope
    });
  }));
 

});

  - Midway Testing

// test/midway/controllers/controllersSpec.js

describe("Midway: Testing Controllers", function() {

  var test, onChange;
  // Async Test로 done 객체 파라미터 전달 
  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      done();
    });
  });

  // 라투트통하여 페이지 변경 성공 이벤트 발생하면 onChange() 호출
  before(function() {
    test.$rootScope.$on('$routeChangeSuccess', function() {
      if(onChange) onChange(); 
    });
  });

  beforeEach(function(done) {
    test.reset(done);
  });

  it('should load the VideosCtrl controller properly when /videos route is accessed', function() {
    onChange = function() {
      test.path().should.eq('/videos');
      var current = test.route().current;
      var controller = current.controller;
      var scope = current.scope;
      expect(controller).to.equal('VideosCtrl');
    };
    test.path('/videos'); // /videos 호출되면 자동으로 onChange() 호출되어 검증 
  });

  it('should load the WatchedVideosCtrl controller properly when /watched-videos route is accessed', function(done) {
    onChange = function() {
      test.path().should.eq('/watched-videos');
      var current = test.route().current;
      var controller = current.controller;
      var params = current.params;
      var scope = current.scope;

      expect(controller).to.equal('WatchedVideosCtrl');
      done();
    };
    test.path('/watched-videos');
  });

});

  - E2E Testing

// test/e2e/controllers/controllersSpec.js
//
describe("E2E: Testing Controllers", function() {

  // 첫 페이지 있는지 검증 
  beforeEach(function() {
    browser().navigateTo('/');
  });

  // videos의 html 화면에서 DIV id="ng-view"의 html 내역 안에 data-app-youtube-listings이 있는지 검증   
  it('should have a working videos page controller that applies the videos to the scope', function() {
    browser().navigateTo('#/');
    expect(browser().location().path()).toBe("/videos");
    expect(element('#ng-view').html()).toContain('data-app-youtube-listings');
  });

  it('should have a working video page controller that applies the video to the scope', function() {
    browser().navigateTo('#/videos/WuiHuZq_cg4');
    expect(browser().location().path()).toBe("/videos/WuiHuZq_cg4");
    expect(element('#ng-view').html()).toContain('app-youtube-embed');
  }); 

}); 



4. Testing Services/Factories 

  - 서비스는 테스트가 가장 쉬운 코들 블럭이다 

  - Service와 Factory에 대해서 테스트 하는 방법

  - Unit Testing

// test/unit/services/servicesSpec.js
//
describe("Unit: Testing Controllers", function() {

  beforeEach(angular.mock.module('App'));

  it('should contain an $appStorage service', inject(function($appStorage) {
    expect($appStorage).not.to.equal(null);
  }));

  // 서비스 존재 유무 검증 
  it('should contain an $appYoutubeSearcher service', inject(function($appYoutubeSearcher) {
    expect($appYoutubeSearcher).not.to.equal(null);
  }));

  it('should have a working $appYoutubeSearcher service', inject(['$appYoutubeSearcher',function($yt) {
    expect($yt.prefixKey).not.to.equal(null);
    expect($yt.resize).not.to.equal(null);
    expect($yt.prepareImage).not.to.equal(null);
    expect($yt.getWatchedVideos).not.to.equal(null);
  }]));

  // $appYoutubeSearcher Factory 검증 
  it('should have a working service that resizes dimensions', inject(['$appYoutubeSearcher',function($yt) {
    var w = 100;
    var h = 100;
    var mw = 50;
    var mh = 50;
    var sizes = $yt.resize(w,h,mw,mh);
    expect(sizes.length).to.equal(2);
    expect(sizes[0]).to.equal(50);
    expect(sizes[1]).to.equal(50);
  }]));

  // $appStorage Factory 검증 
  it('should store and save data properly', inject(['$appStorage',function($storage) {
    var key = 'key', value = 'value';
    $storage.enableCaching();
    $storage.put(key, value);
    expect($storage.isPresent(key)).to.equal(true);
    expect($storage.get(key)).to.equal(value);

    $storage.erase(key);
    expect($storage.isPresent(key)).to.equal(false);

    $storage.put(key, value);
    $storage.flush();
    expect($storage.isPresent(key)).to.equal(false);
  }]));
 

});

  - Midway Testing 

// test/midway/services/servicesSpec.js
//
describe("Midway: Testing Services", function() {

  var test, onChange, $injector;

  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      done();
    });
  });

  // 화면 변경이 있을 경우 onChange() 호출 
  before(function() {
    test.$rootScope.$on('$routeChangeSuccess', function() {
      if(onChange) onChange(); 
    });
  });

  before(function() {
    $injector = test.injector();
  });

  beforeEach(function() {
    //test.reset();
  });

  // $appYoutubeSearcher 를 $injector로 부터 가져옴. findeVideo 호출함 
  it('should perform a JSONP operation to youtube and fetch data', function(done) {
    var $yt = $injector.get('$appYoutubeSearcher');
    expect($yt).not.to.equal(null);

    //this is the first video ever uploaded on youtube
    //so I doubt it will be removed anytime soon
    //and should be a good testing item
    var youtubeID = 'jNQXAC9IVRw';

    $yt.findVideo(youtubeID, false,
      function(q, data) {
        expect(data).not.to.equal(null);
        expect(data.id).to.equal(youtubeID);
        done();
      }
    );
  });
 

});



5. Testing Directives

  - AngularJS입장에서 HTML 코드안에서 컴포넌트 역할이다 

    HTML상에 반복되며 격리될 수 있는 것을 Directive로 만든다. 따라서 HTML 코드 내역은 최소화 되고 불필요한 반복을 줄일 수 있다.

  - Directive가 $scope와 DOM을 가지고 어떻게 작동할지 테스트 해야 한다 

  - XHR을 호출할 수도 있다. 따라서 Unit, Midway, E2E 전부분의 테스트를 수행한다 

  - Unit Testing 

// test/unit/directives/directivesSpec.js
//
describe("Unit: Testing Directives", function() {

  var $compile, $rootScope;

  beforeEach(angular.mock.module('App'));

  // 
  beforeEach(inject(
    ['$compile','$rootScope', function($c, $r) {
      $compile = $c;
      $rootScope = $r;
    }]
  ));

  // app-welcome Directive안에 Welcome 글자와 매치하는지 검증
  it("should display the welcome text properly", function() {
    var element = $compile('<div data-app-welcome>User</div>')($rootScope);
    expect(element.html()).to.match(/Welcome/i);
  })

});

  - Midway Testing

// test/midway/directives/directivesSpec.js
//
describe("Midway: Testing Directives", function() {

  var test, $injector;

  // $injector 얻어오기 
  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      done();
    });
  });

  before(function() {
    $injector = test.$injector;
  });

  // $appYoutubeSearcher 통하여 응답을 받은후 app-youtube-listings Directive를 검증한다. XHR호출이므로 setTimeout 값으로 1초를 준다 
  it("should properly create the youtube listings with the directive in mind", function(done) {
    var $youtube = $injector.get('$appYoutubeSearcher');

    var html = '';
    html += '<div data-app-youtube-listings id="app-youtube-listings">';
    html += ' <div data-ng-include="\'templates/partials/youtube_listing_tpl.html\'" data-ng-repeat="video in videos"></div>';
    html += '</div>';

    var $scope = test.scope();
    var element = angular.element(html);

    $youtube.query('latest', false, function(q, videos) {
      $scope.videos = videos;

      test.directive(element, $scope, function(element) {
        setTimeout(function() {
          var klass = (element.attr('class') || '').toString();
          var hasClass = /app-youtube-listings/.exec(klass);
          expect(hasClass.length).to.equal(1);

          var kids = element.children('.app-youtube-listing');
          expect(kids.length > 0).to.equal(true);
          done();
        },1000);
      });
    });
  });

});

  - E2E Testing

// test/e2e/directives/directivesSpec.js
//
describe("E2E: Testing Directives", function() {

  beforeEach(function() {
    browser().navigateTo('/');
  });

  // 화면을 이동하였을 때 Directive가 표현하는 HTML 내역이 있는지 검증
  it('should have a working welcome directive apply it\'s logic to the page', function() {
    browser().navigateTo('#/videos');
    expect(browser().location().path()).toBe("/videos");
    expect(element('#app-welcome-text').html()).toContain('Welcome');
  });

  it('should have a working youtube listing directive that goes to the right page when clicked', function() {
    browser().navigateTo('#/videos');
    element('.app-youtube-listing').click();
    expect(browser().location().path()).toMatch(/\/videos\/.+/);
  });

});




6. Testing Templates, Partials & Views 

  - 격리된 HTML 코드이다 

  - 앵귤러에서는 ngView 또는 ngInlcude 통하여 렌더링된다 

  - 뷰는 $templateCache 서비스 통해서 캐싱되어 제공된다. 

    테스트시 templateUrl을 주면 캐싱된 mock template을 얻어와 사용할 수 있다 

  - Midway Testing

// test/midway/templates/templatesSpec.js
//
describe("Midway: Testing Requests", function() {

  var test, onChange;
  var $location, $injector, $params;

  // url path 변경시 onChange() 호출
  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      test.$rootScope.$on('$routeChangeSuccess', function() {
        if(onChange) onChange(); 
      });
      done();
    });
  });

  before(function() {
    $injector = test.$injector;
    $location = test.$location;
    $params   = test.$routeParams;
  });

  beforeEach(function() {
    onChange = null;
  });

  // templateUrl을 test.path('XXX') 호출후 template 명칭이 맞는지 검증
  it("should load the template for the videos page properly", function(done) {
    onChange = function() {
      setTimeout(function() {
        var current = test.route().current;
        var controller = current.controller;
        var template = current.templateUrl;
        expect(template).to.equal('templates/views/videos/index_tpl.html');
        onChange = null
        done();
      },1000);
    };
    test.path('/videos?123');
  });
 

});

  - E2E Testing

// test/e2e/templates/templatesSpec.js
//
describe("E2E: Testing Templates", function() {

  beforeEach(function() {
    browser().navigateTo('/');
  });

  // 각 templateUrl 이동시 화면 내역을 검증
  it('should redirect and setup the videos page template on root', function() {
    browser().navigateTo('#/');
    expect(element('#ng-view').html()).toContain('youtube_listing');
  });

  it('should load the watched videos template into view', function() {
    browser().navigateTo('#/watched-videos');
    expect(element('#ng-view').html()).toContain('youtube_listing');
  });

  it('should load the watched video template into view', function() {
    browser().navigateTo('#/videos/123');
    expect(element('#ng-view').html()).toContain('profile');
  });

  it('should redirect back to the index page if anything fails', function() {
    browser().navigateTo('#/something/else');
    expect(element('#ng-view').html()).toContain('youtube_listing');
  });
 

});



7. Testing Filters

  - filter 테스트시에는 $injector 서비스가 필요하고, $injector로 부터 $filter를 얻어온다 

  - Unit Testing

// test/unit/filters/filtersSpec.js
//
describe("Unit: Testing Filters", function() {

  beforeEach(angular.mock.module('App'));

  it('should have a range filter', inject(function($filter) {
    expect($filter('range')).not.to.equal(null);
  }));

  // $filter 서비스를 받아서 range 필터를 얻는다 
  it('should have a range filter that produces an array of numbers', inject(function($filter) {
    var range = $filter('range')([], 5);
    expect(range.length).to.equal(5);
    expect(range[0]).to.equal(0);
    expect(range[1]).to.equal(1);
    expect(range[2]).to.equal(2);
    expect(range[3]).to.equal(3);
    expect(range[4]).to.equal(4);
  }));

  it('should return null when nothing is set', inject(function($filter) {
    var range = $filter('range')();
    expect(range).to.equal(null);
  }));

  // range filter의 input에 대한 검증
  it('should return the input when no number is set', inject(function($filter) {
    var range, input = [1];
    range = $filter('range')(input);
    expect(range).to.equal(input);

    range = $filter('range')(input, 0);
    expect(range).to.equal(input);

    range = $filter('range')(input, -1);
    expect(range).to.equal(input);

    range = $filter('range')(input, 'Abc');
    expect(range).to.equal(input);
  }));
 

});

  - Mideway Testing

// test/midway/filters/filtersSpec.js

//
describe("Midway: Testing Filters", function() {

  var test, onChange, $filter;

  // 최초 한번 미리 $filter 서비스를 얻어온다 
  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      test.$rootScope.$on('$routeChangeSuccess', function() {
        if(onChange) onChange(); 
      });

      $filter = test.filter();

      done();
    });
  });

  beforeEach(function() {
    test.reset();
  });

  it('should have a working range filter', function() {
    expect($filter('range')).not.to.equal(null);
  });

  // html 템플릿 DOM을 만들어서 append 하고, Directives에 사용한 range에 대해서 검증
  it('should have a working filter that updates the DOM', function(done) {
    var id = 'temp-filter-test-element';
    var html = '<div id="' + id + '"><div class="repeated" ng-repeat="n in [] | range:10">...</div></div>';
    var element = angular.element(html);
    angular.element(document.body).append(html);

    test.directive(element, test.scope(), function(element) {
      var elm = element[0];
      setTimeout(function() {
        var kids = elm.getElementsByTagName('div');
        expect(kids.length).to.equal(10);
        done();
      },1000);
    });
  });

});

  - E2E Testing 

// test/e2e/filters/filtersSpec.js

//
describe("E2E: Testing Filters", function() {

  beforeEach(function() {
    browser().navigateTo('/');
  });

  // range 필터가 사용된 화면으로 이동하였을 경우 화면이 정상적으로 표시 되는지 검증
  it('should have a filter that expands the stars properly', function() {
    browser().navigateTo('#/videos/WuiHuZq_cg4');
    expect(repeater('#app-youtube-stars > .app-youtube-star').count()).toBeGreaterThan(0);
  });

});



<참조>

  - 원문 : Full Spectrum Test in AngularJS 

  - Javascript 테스트를 어렵게 하는 6가지

  - Service Test 전략 동영상

    


  - AngularJS를 위한 새로운 E2E Test Framework "Protractor"

  - Protractor로 E2E 테스트하기 : 기존 Karma E2E 보다 쉬워진 느낌이다. 30분 부터 보시라

    


posted by 윤영식
2013. 10. 20. 21:12 Testing, TDD/Test First

AngularJS 기반하에 TDD를 수행하거나 테스트 코드를 작성할 때 적합한 툴과 개념에 대해 이해를 하자



1. Unit Testing

  - 단위 기능 테스트 

  - 추천 단위 테스트 툴

    + Mocha : 단순하며 유연한 테스트 프레임워크 (추천)

    + Jasmine : Behavior-Driven Development Framework for testing Javascript (추천)

    + QUnit : jQuery에서 사용

  - 단위 테스트 만들기 

    + 사용자 스토리 기반으로 만들어 보자 

       "사용자로써 나의 앱에 로그인을 하면 대시보드 페이지를 본다"

    + Story -> Features -> Units 로 구분을 한다 

       "로그인 폼"을 하나의 기능으로 선택한다

       하나의 스토리는 여러 기능의 합집합이며, 각 기능의 정상작동 검증(Verification)을 거쳐서 하나의 스토리는 완성된다 

    + 로그인 폼에 대한 Spec을 만든다 (BDD)

describe('user login form', function() {


    // critical : 기능의 검증

    it('ensure invalid email addresses are caught', function() {});

    it('ensure valid email addresses pass validation', function() {});

    it('ensure submitting form changes path', function() { });


    // nice-to-haves

    it('ensure client-side helper shown for empty fields', function() { });

    it('ensure hitting enter on password field submits form', function() { });


});

   

  - 위의 테스트 Spec을 AngularJS로 단위 테스트 만들기 

    + 파일 : angular.js, angular-mocks.js, app.js(모듈명-App), loginController.js(LoginCtrl),  loginService.js(LoginService)

    + beforeEach에서 angular.mock.module 통해 모듈을 셋업한다 

       beforeEach에서 angular.mock.inject 통해 controller에서 $scope 셋업한다 (참조) 

    + it 에서 inject 통하여 service를 셋업한다 (참조)

describe("Unit Testing Examples", function() {


  beforeEach(angular.mock.module('App')); 


  it('should have a LoginCtrl controller', function() {

    expect(App.LoginCtrl).toBeDefined();

  });


  it('should have a working LoginService service', inject(['LoginService',

    function(LoginService) {

      expect(LoginService.isValidEmail).not.to.equal(null);


      // test cases - testing for success

      // 검증을 위한 테스트 데이터 

      var validEmails = [

        'test@test.com',

        'test@test.co.uk',

        'test734ltylytkliytkryety9ef@jb-fe.com'

      ];


      // test cases - testing for failure

      var invalidEmails = [

        'test@testcom',

        'test@ test.co.uk',

        'ghgf@fe.com.co.',

        'tes@t@test.com',

        ''

      ];


      // you can loop through arrays of test cases like this

      for (var i in validEmails) {

        var valid = LoginService.isValidEmail(validEmails[i]);

        expect(valid).toBeTruthy();

      }

      for (var i in invalidEmails) {

        var valid = LoginService.isValidEmail(invalidEmails[i]);

        expect(valid).toBeFalsy();

      }


    }]) // end inject 

  ); // end it 

}); // end describe



2. Integration Testing 

  - 통합 테스트는 end-to-end 테스트(E2E)이고, 수행순서대로 차례로 수행해 보는 것이다. 

     그리고 애플리케이션과 UI의 상태를 검증한다. 심지어 Visual적인 부분에 대한 검증도 가능하다 

  - 추천 도구 

    + Karma : AngularJS에서 공식사용 (추천)

    + CasperJS : headless browser로 사용 

    + PhantomJS : headles brower

  - 통합 테스트 만들기 

    + "사용자로써 나의 앱에 로그인하여 대시보드 페이지를 본다"

describe('user login', function() {


  // 수행 및 결과 상태의 검증

  it('ensures user can log in', function() {

    // expect current scope to contain username

  });

  it('ensures path has changed', function() {

    // expect path to equal '/dashboard'

  });


});


  - 위의 테스트 Spec을 AngularJS로 통합 테스트 만들기 (참조)

    + 최근에는 Angular Scenario Test Runner와 같은 Protractor E2E test framework이 새롭게 소개되고 있다 

describe("Integration/E2E Testing", function() {


  // start at root before every test is run

  beforeEach(function() {

    browser().navigateTo('/');

  });


  // test default route

  it('should jump to the /home path when / is accessed', function() {

   // 루트 패스로 이동 - 로그인 화면 

    browser().navigateTo('#/');

    expect(browser().location().path()).toBe("/login");

  });


  it('ensures user can log in', function() {

    // 로그인 화면인지 체크

    browser().navigateTo('#/login');

    expect(browser().location().path()).toBe("/login");


    // assuming inputs have ng-model specified, and this conbination will successfully login

    // 테스트 데이터를 통해 브라우져 로그인 submit 버튼 click 이벤트 발생시킴 

    input('email').enter('test@test.com');

    input('password').enter('password');

    element('submit').click();


    // logged in route

    // 로그인이 성공했을 경우 대시보드 패스에 있는지 검증

    expect(browser().location().path()).toBe("/dashboard");


    // my dashboard page has a label for the email address of the logged in user

    // DIV의 id="email"의 html 값이 로그인시 입력한 값인지 검증 

    expect(element('#email').html()).toContain('test@test.com');

  });


  it('should keep invalid logins on this page', function() {

    // 로그인 화면 인지 검증

    browser().navigateTo('#/login');

    expect(browser().location().path()).toBe("/login");


    // assuming inputs have ng-model specified, and this conbination will successfully login

    // 잘 못된 값을 넣었을 가지고 로그인 버튼 클릭 

    input('email').enter('invalid@test.com');

    input('password').enter('wrong password');

    element('submit').click();


    // DIV id="message"에 failed메세지 있는지 검증 

    expect(element('#message').html().toLowerCase()).toContain('failed');


    // logged out route

    // 제대로 로그인을 못했으므로 로그인 패스 그대로인지 검증 

    expect(browser().location().path()).toBe("/login");

  }); // end it 

}); // end describe 



3. Mocking Services and Modules in AngularJS

  - 목(Mock) 서비스를 이용한 테스트 방법 

  - 목 오브젝트를 사용하는 이유는 외부적인 요소 의존적인 부분을 제거하고 테스트 하기 위해서 이다 

  - ngMock.$httpBackend : service에서 http login 호출을 하면 응답 JSON 객체를 넘겨주는 mock 객체를 만든다 

it('should get login success',

  inject(function(LoginService, $httpBackend) {


    $httpBackend.expect('POST', 'https://api.mydomain.com/login')

      .respond(200, "[{ success : 'true', id : 123 }]");


    LoginService.login('test@test.com', 'password')

      .then(function(data) {

        expect(data.success).toBeTruthy();

    });


  $httpBackend.flush();

});



<참조>

  - 원문 : Best Practices in AngularJS

  - AngularJS Controller Unit Test 방법

  - AngularJS Service Unit Test 방법

  - Full Spectrum Testing(E2E) in Angular with Karma (필독)

  - Introducing E2E Testing in Angular 동영상

    

posted by 윤영식
prev 1 2 next