MVC 패턴을 구현하는 자바스크립트 프레임워크 AngularJS 활용편

Size: px
Start display at page:

Download "MVC 패턴을 구현하는 자바스크립트 프레임워크 AngularJS 활용편"

Transcription

1 Hanbit ebook Realtime 41 MVC 패턴을구현하는자바스크립트프레임워크 AngularJS 활용편 AngularJS 브래드그린, 샤이엄세샤드리지음 / 김지원옮김

2 이도서는 O REILLY 의 AngularJS 의번역서입니다.

3 MVC 패턴을구현하는자바스크립트프레임워크 AngularJS 활용편

4 MVC 패턴을구현하는자바스크립트프레임워크 AngularJS 활용편 초판발행 2013 년 9 월 13 일 지은이브래드그린, 샤이엄세샤드리 / 옮긴이김지원 / 펴낸이김태헌펴낸곳한빛미디어 ( 주 ) / 주소서울시마포구양화로 7길 83 한빛미디어 ( 주 ) IT출판부전화 / 팩스 등록 1999년 6월 24일제 호 ISBN / 정가 9,900원 책임편집배용석 / 기획김병희 / 편집이세진디자인표지여동일, 내지스튜디오 [ 밈 ], 조판김현미마케팅박상용, 박주훈 이책에대한의견이나오탈자및잘못된내용에대한수정정보는한빛미디어 ( 주 ) 의홈페이지나아래이메일로알려주십시오. 한빛미디어홈페이지 / 이메일 Published by HANBIT Media, Inc. Printed in Korea Copyright c 2013 HANBIT Media, Inc. Authorized Korean translation of the English edition of AngularJS, ISBN c 2013 Brad Green, Shyam Seshadri. This translation is published and sold by permission of O Reilly Media, Inc., which owns or controls all rights to publish and sell the same. 이책의저작권은오라일리사와한빛미디어 ( 주 ) 에있습니다. 저작권법에의해보호를받는저작물이므로무단복제및무단전재를금합니다. 지금하지않으면할수없는일이있습니다. 책으로펴내고싶은아이디어나원고를메일 로보내주세요. 한빛미디어 ( 주 ) 는여러분의소중한경험과지식을기다리고있습니다.

5 저자소개 지은이 _ 브래드그린Brad Green 구글 AngularJS 프로젝트팀에서엔지니어관리자를맡고있으며, 접근성과지원공학을총괄감독한다. 구글에입사하기전에는인터넷기업의마케팅도구를만들어팔던 AvantGo 사에서초창기모바일웹개발자로근무하다가, 출장요식업에뛰어들어고단한몇년을보냈다. 대학을졸업하고 NeXT Computer 사에서스티브잡스의지휘아래밑에서데모소프트웨어를만들고잡스의슬라이드프레젠테이션을디자인했던것이브래드의첫직장경험이다. 브래드는아내와두자녀를데리고캘리포니아주마운틴뷰에살고있다. 지은이 _ 샤이엄세샤드리Shyam Seshadri Fundoo Solutions 사의사주이자 CEO다. AngularJS에관해컨설팅하고워크숍을개최한다. 인도시장을겨냥한혁신적제품개발에주력하며, AngularJS를주제로한워크숍을운영하고컨설팅한다. Fundoo Solutions 사를창립하기전에는하이데라바드에있는 Indian School of Business에서 MBA 과정을마쳤다. 샤이엄은대학졸업후첫직업으로구글에서다수의프로젝트를진행했다. 그중에는 AngularJS가처음으로사용된구글피드백 (Google Feedback) 프로젝트도있다. 그리고다양한내부도구도제작했다. 현재는인도나비뭄바이에서회사를운영하고있다.

6 역자소개 옮긴이 _ 김지원 웹기술뿐아니라온갖분야에발을뻗고싶어하는바람기를지녔지만역부족이다. 배워야할것이갈수록늘어나시간의결핍을느낀다. 워드프레스, 프라이드치킨, 꿀, 분재, 컴퓨터음악을좋아한다. 기술문서, 매뉴얼, 유비쿼터스관련논문을번역한바있고해외논문 DB 구축관련작업에도참여했다. 번역서로는 한권으로끝내는정규표현식 ( 한빛미디어, 2010), 웹표준가이드 : HTML5+CSS3 ( 한빛미디어, 2010), 프로젝트로배우는 HTML5+ 자바스크립트 ( 한빛미디어, 2012), 리팩토링 ( 한빛미디어, 2012), 엘리멘틀디자인패턴 ( 한빛미디어, 2013), HTML5+CSS3 디자인패턴 ( 한빛미디어, 2013), HTML5 웹소켓프로그래밍 ( 한빛미디어, 2013), AngularJS 기초편 ( 한빛미디어, 2013) 등이있다.

7 저자서문 Angular 프레임워크의기원은 2009 년의구글피드백 Google Feedback 이라는프로젝 트로거슬러올라간다. 필자는테스트가능한코드를작성하면서수개월간개발속 도와기능의문제로난관을겪었다. 6 개월여간작성한프론트엔드 front-end 코드가 대략 17,000줄이었다. 그런데당시프로젝트팀원이던미스코헤브리Misko Hevery 는자신이취미로작성한오픈소스라이브러리를사용하면 2 주도안걸려서우리 가작성한코드전체를새로작성할수있을거라고큰소리쳤다. 필자는프로젝트가 2주쯤지연돼도별문제는없을것이고설령미스코가장담한대로기한내에다시작성하지못하더라도기한에쫓겨허둥대는미스코의표정을보는재미라도있겠다는생각에그렇게해보라고했다. 예상대로미스코는기한을넘겨 3주만에완료했다. 그래도 6개월이걸렸던개발을 3주라는단시간에재현했다는점에우리팀전원은경악했는데, 더놀랍게도미스코가새로작성한애플리케이션의코드분량이원래의 17,000줄의 1/10도안되는 1,500줄에불과했다. 미스코가이뤄낸성과는추진해볼가치가있어보였다. 미스코가간단한선언문서로창안했던각종개념을중심으로해서, 미스코와나는웹개발자의경험을간소화할팀을만들기로했다. 이책의공동저자인샤이엄세샤드리 Shyam Seshadri 는구글피드백팀에서 Angular의첫출시애플리케이션개발을지휘했다. 그때부터우린구글의여러팀과수백명의오픈소스기부자의도움을받아 Angular 를개발했다. 많은개발자가일상적인작업에 Angular 프레임워크를이 용하며엄청난 Angular 지원망에기여한다. 우리는여러분이나눠주는지식을얻게되리란생각에감개무량하다.

8 감사의글 Angular 프레임워크를탄생시킨미스코헤브리에게각별히고마움을전한다. 미스코덕택에웹애플리케이션작성법을기존과전혀다른방식으로생각하고실천할수있었다. 이고르미나 Igor Minar 는 Angular 프로젝트의안정화와체계화에기여했고활성화된지금의오픈소스커뮤니티의모체를만들었다. 보이타지나 Vojta Jina 는 Angular의많은부분을작성했으며덕분에우리는테스트를유례없이신속하게할수있었다. 나오미블랙 Naomi Black, 존린퀴스트 John Lindquist, 매사이어스마셔스니멜라 Mathias Matias Niemela 는숙련된솜씨로편집을도와주었다. 앞서나열한모든분들과더불어, 다방면에서도움을주고실시간애플리케이션제작과정에서피드백을통해우리에게 Angular의가치있는사용법을알려준 Angular 커뮤니티분들께감사의인사를남긴다. 브래드그린, 샤이엄세샤드리

9 역자서문 왜 AngularJS 인가? 개발자가구글의 AngularJS 플랫폼을선택할수밖에없는이 유는다음과같다. 양방향데이터바인딩이가능하다 - AngularJS로개발한애플리케이션은클라이언트에서서버로뿐만아니라서버에서클라이언트로도실시간변경감지가이뤄진다. 감시, 리스너, 캡처기능을통해개발한코드가실행되고모델을조작한후발생하는변경사항을감시한다. 모델, 뷰, 컨트롤러, 서비스등여러구성요소로분리된다 - 지시어, 필터, 모듈등의추상객체를이용해균형을맞출수있다. 이로써복잡도의감소와관심사의분리라는두마리토끼를얻을수있다. 편리하고친숙한패턴이많다 - MVC나종속물주입같은유명한패턴외에도종속물관리같은다수의패턴이들어있어서체계적인구성으로개발할수있다. 테스트용코드를쉽게작성할수있다 - AngularJS 공식온라인강좌페이지에도 Jasmine 문법을사용한단위테스트와클라이언트-서버테스트를코드로작성하는방법이예시돼있다. 모든프레임워크가그렇듯비록 AngularJS 역시완벽할순없지만, 사소한단점에 비해얻을수있는것이많다. AngularJS 에관한전반적인내용이이책의본문에 자세히설명돼있으니자세한얘기는본문을숙지하기바란다. AngularJS 를개발에사용하면길고복잡한코딩의분량을획기적으로줄일수 있다. 아주간단하고코딩길이가짧은프로젝트라면물론큰이득을얻지못할 수도있지만, 데이터수정시에빠른반응성이요구되는 UI 를구상하고있다면

10 AngularJS는개발에있어서반드시고려할만하다. 플랫폼에이미내장돼있는지시어말고도개발자가직접정의한지시어를템플릿에사용함으로써모델, 컨트롤러와바로연결이가능한점은강력한기능이다. 종속물주입또한상위의기능에필요한종속물 ( 종속객체, 종속함수, 종속모듈등 ) 을마치혈관으로연결된링거에주사기로항생제를주입하듯이손쉽게끼워넣음으로써매우직관적이며메소드체인형태로호출이가능하다. 이모든잡다한서론을뒤로하고지금당장본문의첫페이지로가서어떤놀라운 장점이있고어떤부분이자신의개발에필요한지살펴보자. 번역을마무리하며, 김지원

11 도움을주신분들 베타테스터 _ 고지은 졸업하고나서알고싶은게더많은, 컴퓨터공학을전공한취업준비생이다. 앞으 로나아가기위해다양한전공책들에싸여지내고있다. 베타테스터 _ 김광남프로그래머로서, 많은사람이무료로사용할수있는프로그램을만드는게삶의목표다. 게임을개발하다현재는노래방회사에서 PC, SmartPhone, TV용노래방을개발하고있다. 나의아내효성아! 언제나사랑한다. 베타테스터 _ 김종호스타트업을통해새로운가치를만들길원하는학생이다. C/C++ 을사용하며지루해했으나안드로이드개발을통해프로그래밍에눈을떴다. 지금은파이썬과다양한웹기술을이용하여웹서비스를만들어서재미있는시도를하고있다. 서버와플랫폼에도관심이많으며풀스택개발자가되기를희망한다. 베타테스터 _ 박정춘새로운기술과프로그래밍언어에관심이많은백엔드개발자다. 최근에오픈소스와함수형프로그래밍에빠져있으며, 좋은 E-Commerce 서비스를만들기위해노력하고있다. 베타테스터 _ 한상곤리눅스에서개발자생활을시작하여, HTML과 Spring 영역까지뚜벅뚜벅걸어왔다. 어떻게왔는지, 어디로갈지는모르지만매일매일새로운것을설계하고만드는재미에푹빠져있다. 요즘은 Golang과파이썬에빠져서허우적거리고있다.

12 대상독자및예제파일 초급초중급중급중고급고급 이도서는 AngularJS의핵심을빠르게확인하고싶은독자를대상으로한다. 도서의내용을보다잘이해하려면, HTML과자바스크립트를어느정도알고있어야한다. 다음과같은독자들에많은도움이될것이다. 규모있는웹애플리케이션프로젝트의실무개발자 프레임워크기반으로자바스크립트에익숙해지려는웹퍼블리셔 jquery 입문이상으로나아가려는자바스크립트개발자 이도서의예제소스코드는다음웹사이트에서내려받을수있다. https://github.com/shyamseshadri/angularjs-book ( 영문버전 ) ( 한글버전 ) 영문버전은 AngularJS 버전을사용하였으며, 한글버전은 AngularJS 을사용하였다. 이도서에있는예제는 AngularJS 을사용하여소스코 드를테스트하였다. 소스코드에는구글 AngularJS 프레임워크파일의 URL 로인클루딩되어있다. 하 지만소스코드의간결성을위해일부코드는 AngularJS 사용하였다. 이도서에서 사용한 AngularJS 프레임워크파일은다음웹사이트에서내려받을수있다.

13 한빛 ebook 리얼타임 한빛 ebook 리얼타임은 IT 개발자를위한 ebook 입니다. 요즘 IT 업계에는하루가멀다하고수많은기술이나타나고사라져갑니다. 인터넷을아무리뒤져도조금이나마정리된정보를찾는것도쉽지않습니다. 또한잘정리되어책으로나오기까지는오랜시간이걸립니다. 어떻게하면조금이라도더유용한정보를빠르게얻을수있을까요? 어떻게하면남보다조금더빨리경험하고습득한지식을공유하고발전시켜나갈수있을까요? 세상에는수많은종이책이있습니다. 그리고그종이책을그대로옮긴전자책도많습니다. 전자책에는전자책에적합한콘텐츠와전자책의특성을살린형식이있다고생각합니다. 한빛이지금생각하고추구하는, 개발자를위한리얼타임전자책은이렇습니다. 1. ebook Only - 빠르게변화하는 IT 기술에대해핵심적인정보를신속하게제공합니다. 500페이지가까운분량의잘정리된도서 ( 종이책 ) 가아니라, 핵심적인내용을빠르게전달하기위해조금은거칠지만 100페이지내외의전자책전용으로개발한서비스입니다. 독자에게는새로운정보를빨리얻을수있는기회가되고, 자신이먼저경험한지식과정보를책으로펴내고싶지만너무바빠서엄두를못내는선배, 전문가, 고수분에게는보다쉽게집필할수있는기회가될수있으리라생각합니다. 또한새로운정보와지식을빠르게전달하기위해 O'Reilly의전자책번역서비스도하고있습니다. 2. 무료로업데이트되는, 전자책전용서비스입니다. 종이책으로는기술의변화속도를따라잡기가쉽지않습니다. 책이일정분량이상으로집필되고정리되어나오는동안기술은이미변해있습니다. 전자책으로출간된이후에도버전업을통해중요한기술적변화가있거나저자 ( 역자 ) 와독자가소통하면서보완하여발전된노하우가정리되면구매하신분께무료로업데이트해드립니다.

14 3. 독자의편의를위하여 DRM-Free로제공합니다. 구매한전자책을다양한 IT 기기에서자유롭게활용할수있도록 DRM-Free PDF 포맷으로제공합니다. 이는독자여러분과한빛이생각하고추구하는전자책을만들어나가기위해독자여러분이언제어디서어떤기기를사용하더라도편리하게전자책을볼수있도록하기위함입니다. 4. 전자책환경을고려한최적의형태와디자인에담고자노력했습니다. 종이책을그대로옮겨놓아가독성이떨어지고읽기힘든전자책이아니라, 전자책의환경에가능한한최적화하여쾌적한경험을드리고자합니다. 링크등의기능을적극적으로이용할수있음은물론이고글자크기나행간, 여백등을전자책에가장최적화된형태로새롭게디자인하였습니다. 앞으로도독자여러분의충고에귀기울이며지속해서발전시켜나가도록하겠습니다. 지금보시는전자책에소유권한을표시한문구가없거나타인의소유권한을표시한 문구가있다면위법하게사용하고있을가능성이높습니다. 이경우저작권법에의해 불이익을받으실수있습니다. 다양한기기에사용할수있습니다. 또한한빛미디어사이트에서구입하신후에는횟수에 관계없이다운받으실수있습니다. 한빛미디어전자책은인쇄, 검색, 복사하여붙이기가가능합니다. 전자책은오탈자교정이나내용의수정 보완이이뤄지면업데이트관련공지를이메일로 알려드리며, 구매하신전자책의수정본은무료로내려받으실수있습니다. 이런특별한권한은한빛미디어사이트에서구입하신독자에게만제공되며, 다른 사람에게양도나이전은허락되지않습니다.

15 차례 들어가기전에필수용어와개념정리 1 종속물주입 (Dependency Injection) 1 이책에서 종속물주입 이라는용어를사용한이유 2 프라미스인터페이스와 $q 서비스객체 5 01 서버와통신하기 $http 서비스를사용한통신 단위테스트실시 REST 기반의리소스사용하기 $q와프라미스 응답가로채기 보안고려사항 지시어 지시어와 HTML 유효성검사 API 개요 정리 그밖의사안 $location AngularJS 모듈메소드 $on, $emit, $broadcast 를사용하여스코프간통신하기 80

16 3.4 쿠키 국제화와지역화 HTML 안전화와 Sanitize 모듈 총정리와레시피 jquery의 Datepicker를캡슐화하기 팀목록애플리케이션 ( 필터링과컨트롤러통신 ) AngularJS로파일올리기 Socket.IO 사용하기 간단한페이지구분서비스 서버를이용한작업과로그인 맺음말 130

17 들어가기전에 필수용어와개념정리 종속물주입 (Dependency Injection) 01 종속물주입은하드코딩한필요기능 (depencency, 이것은 결합도 가아님 ) 을제 거 / 해제하거나변경할수있도록적용하는소프트웨어디자인패턴이다. 예컨대플러그인을동적으로로딩하거나테스트단계에서스텁객체나목 mock 객체를선택또는제품화단계에서실제객체를선택하는작업에종속물주입패턴을적용하면수월해진다. 종속물주입패턴은대상의요건을파악해서종속요소 ( 객체, 값등 ) 를대상에자동으로주입한다. 종속물조회 dependency lookup 라는패턴도있는데, 이패턴은종속물주입을위한순과정과역과정에해당한다. 종속물조회는호출객체가컨테이너객체에특정이름이나특정타입으로된객체를요청하는패턴이다. 종속물주입패턴은최소한다음세요소로구성된다. 종속된소비객체 consumer 구성요소의종속물 ( 인터페이스콘트랙트로정의됨 ) 을선언하는코드 주입객체 injector02 종속객체안에는작업수행에필요한소프트웨어구성요소를작성한다. 주입객체는 01 출처 : Wikipedia의 Dependency Injection 페이지 02 프로바이더 나 컨테이너 라고도하며, 이객체안에종속물을넣어서주입한다. 이객체는요청을받으면주어진종속인터페이스를구현하는클래스를인스턴스화한다. 들어가기전에필수용어와개념정리 1

18 어떤구체클래스 03 가종속객체의요건에부합하는지를판단하여, 조건에부합하 는구체클래스를종속객체에제공한다. 기존방식으로소프트웨어를개발할때는어떤구체클래스를사용할지를종속객체가스스로판단했다. 그러나종속물주입패턴에서는주입객체에이판단이위임되므로, 주입객체는종속콘트랙트인터페이스를적절하다고판단한구체클래스로교체할수있다. 이것은컴파일이아니라런타임시에이뤄진다. 이책에서 종속물주입 이라는용어를사용한이유 dependency: 종속물. 기본적으로는 종속객체 를뜻하지만언어와문맥에따라종속객체, 종속라이브러리, 종속함수, 종속리소스, 종속모듈등현재객체의기능수행에필요하여컨테이너로캡슐화해주입할수있는모든대상을지칭할수있다. 기존에는 dependency를 의존성 이나 의존관계 로번역했는데, 이러한용어는매우막연하고두리뭉실한뜬구름같아서용어의의미를제대로설명할수없었다. 프로그래밍의흐름은대충알지만이런용어의개별적인개념을정확하게알지못하고넘어간다면, 후에더욱복잡한기술로진화하고많은파생개념이생길수록혼란이심해질것이다. 의존성 이라고하면의존하는 성질 을뜻한다. 따라서상위코드에꼭필요한부품과도같은하위코드를 의존성 ' 이라고표현해서는그의미를제대로전달할수없다. 이것은 IT 기술분야에서용어를모든문맥에가장부합하게끔고민을거쳐선택했다기보다는, 마치영어사전을뒤졌을때나오는단순한사전적의미중하나를고른듯한성의없는용어선택이아닐수없다. 종속되는객체나모듈은주입될 03 추상클래스의반대개념으로일반적인클래스를말한다. 인스턴스를만들수있는클래스로, 추상메소드를가질수없다. 들어가기전에필수용어와개념정리 2

19 대상으로서구체적인실체이지 성질 이나 관계 가아니다. 종속물을주입하면의존성이나의존관계가형성되는것이지, 의존성이나의존관계자체를주입하는것이아니다. 그리고 종속객체 라는용어를사용하면종속된관계의대상이객체만으로한정돼버리기때문에적절하지않다. 문맥과기술에따라달라지겠지만, 적어도이책에서는 dependency가단지종속객체뿐아니라종속모듈, 서비스, 함수등을통칭하기때문에포괄적인뜻을담은 종속물 로번역했다. 물론자바스크립트에서도모든것 ( 함수, 모듈, 서비스등 ) 을아우르는개념이객체이므로종속객체라는표현도괜찮기는하지만설명할때는모듈, 서비스라고표기하지 모듈객체, 서비스객체 라는식으로표기하지않기때문에상위개념으로한정되는느낌을주지않기위해서 종속물 을택한것이다. 1. 이것을 의존- 으로번역하면안되는이유 : dependency를의존, 의존성, 의존관계, 의존물등으로번역하면 dependent ~ 라는표현이자주병행등장하는데이것을일관성에따라 의존하는 ~ 으로번역해야하므로잘못된뜻이돼버린다. 왜냐하면 dependent function은종속된함수인데, 오히려이함수를 의존하는함수 로해석하게되므로틀린의미가된다. 반면에 종속된함수 라고해석하면의미가맞으므로모든문맥에서일관되게올바른뜻을가지려면반드시 종속 으로만번역해야하는것이다. 그리고개념을설명하는영상이나도표자료를검색해보면 dependency injection 개념을설명할때는항상벤다이어그램을사용해포함관계를나타내는데, 이를보더라도역시 종속 이더욱적절하다. 이것을거부감없이이해하려면기존에널리쓰이던 의존성주입 이라는용어의편견을떨쳐야한다. 2. 이것을 부속- 으로번역하면안되는이유 : 부속 과 종속 은의미상비슷하다. 그러나다음과같은미묘하고도중대한의미적차이가있다. 부속 : 주된사물이나기관에딸려서붙음 ( 공간적 / 위치적소속과기능적인구성요소임을동시에뜻함 ). 들어가기전에필수용어와개념정리 3

20 종속 : 자주성이없이주가되는것에딸려붙음 ( 공간적 / 위치적뉘앙스에비해 주종관계에서필요에따라주가이용할수있는종의역할을한다는의미가 더강하다 ). 따라서 종속 은주 ( 주입객체 ) 가있어야호출되며종속된종 ( 종속객체 ) 은주에게필요한기능을제공한다는뜻에서 종속 을사용하는것이올바르다. 물론, 부속 ' 에도미약하나마기능적인구성요소라는의미가있지만, 부속 은 종속 에비해의존적인관계형성에있어훨씬약한뉘앙스를풍기는낱말이므로 종속 을택했다. 3. injection을 삽입 으로번역하는것이바람직하지않은이유 : 국어사전에있는의미를그대로생각하면 injection은액체나사상등을주입할때쓰는말이므로, 종속물을현객체로집어넣는이상황에서 삽입 (insertion) 이사전적의미로보아바른용어선택인것처럼보일수있다. 그러나 IT 분야의각종개념을설명하는표현이나용어를정할때는 메타포 기법을사용한다. 메타포란현실세계 ( 일상, 제품, 상식, 각학문분야등 ) 에서흔히쓰이는낱말과표현에빗대는것을말한다. 메타포에따른용어와표현을사용하면개발자는어떠한개념이든쉽게이해할수있기때문이다. 중요한것은, 어차피영어권국가에서만든용어고새로운개념및용어가현재에는없지만미래의어느순간기존용어에서분리 / 파생되거나신생될수있기에영문용어를실정에맞게의역하되가능하면일대일로대응시켜정립하는것이바람직하다. 미래엔 insertion이라는용어가새로생겨나병용될수도있기때문에 injection을굳이사전적의미로만고집해 삽입 으로번역할것이아니라상위객체에종속객체를주사기로밀어넣는것에빗댄메타포에의한용어를채택하는것이좋다. 그렇게하면 dependency injection이라는영문용어를보고 주입 을바로떠올릴수있고 종속물주입 이라고하면곧바로 dependency injection 을떠올릴수있다. 만약 종속물삽입 이라고용어를정한다면그걸들은사람이영문을생각해내 들어가기전에필수용어와개념정리 4

21 야할상황 ( 외국인개발자와협업등 ) 이닥쳤을때 dependency insertion이던가? 하는혼동을초래할수도있다. 삽입 을주장하기위해 객체 를굳이 고체 라고여길필요는없다 ( 심지어현실의물질도아니다 ). 관점에따라객체는 액체 일수도있고 기체 일수도있다. 핵심은결코 고체 로한정할근거가없다는데있다. 용어를정하기전에는반드시비슷한여러단어중에서그단어를원어민필자가사용한데에는나름의이유가있게마련이라는생각을해봐야한다. 참고 ) dependency injection 패턴에는구성원인 injector 개념이함께등장한다. injector는 주입객체 로번역해야하며, 프로바이더 ( 프로바이더객체 ) 나컨테이너 ( 컨테이너객체 ) 라고도한다. 프라미스인터페이스와 $q 서비스객체 프라미스프라미스는비동기적으로수행되는작업의결과를대변하는객체와교류하는인터페이스다. 프라미스 promise 는하나의작업완료로반환된결과값을대변한다. 프라미스는세가지 (unfulfilled: 미이행, fulfilled: 이행완료, failed: 실패 ) 중하나의상태에처할수있다. 프라미스가 fulfilled( 이행완료 ) 상태나 failed( 실패 ) 상태면프라미스의값은절대로변해선안되며, 자바스크립트에서의값이나기본타입 / 객체 ID와마찬가지로변할수없다. 프라미스의 변할수없는 특징덕에리스너에예상치못한기능변화가생길수있는부작용이예방되며, 프라미스를다른함수에전달해도호출객체에영향이미치지않는다. 이것은 호출하는객체 의변수가 호출되는객체 에의해변경될염려없이기본타입을함수에전달할수있는것과도같다. 함수가값을반환하는것이불가능하거나차단하지않으면, 예외가통지될때그 함수는값대신프라미스를반환하면된다. 프라미스는함수가최종적으로제공할 들어가기전에필수용어와개념정리 5

22 가능성이있는반환값이나통지예외를대변하는인터페이스객체다. 지연을극복 하기위해프라미스를원격객체의프록시로사용할수도있다. $q 객체 $q: 크리스코우얼 Kris Kowal 의 q 도구 04 에들어있는프라미스 API와지연 API에서착안해만든 AngularJS의서비스객체중하나지만, AngularJS의 $q 객체는크리스코우얼의 q와는차이가있다. $q 서비스의종속물은 $rootscope 서비스다. $q 객체의메소드로는다음과같은것들이있다. 1. all( 프라미스배열 ) 메소드모든입력프라미스가해독될때해독되는다수의프라미스를하나의프라미스 로묶는메소드다. 매개변수 : { Array.< 프라미스 > } 프라미스배열 반환값 : { 프라미스 } - 값배열로해독될하나의프라미스를반환한다. 사용되는값배열의각값은같은인덱스에위치한프라미스에일대일대응된다. 프라미스중어느하나라도 거부됨 으로해독되면반환되는프라미스역시 거부됨 상태 ( 이벤트 ) 로해독된다. 2. defer( ) 메소드 나중에끝마칠작업을대변하는지연객체를생성한다. 반환값 : { Deferred } - deferred 의새로운인스턴스를반환한다. 04 https://github.com/kriskowal/q 들어가기전에필수용어와개념정리 6

23 3. reject( 사유 ) 메소드 특정사유로거부됨 으로해독될프라미스를생성한다. reject API는프라미스체인에서 거부됨 을전달할때사용한다. 프라미스체인의맨끝에있는프라미스를처리할때는신경쓸필요가없다. 지연 / 프라미스구조를이미잘알고있는 try/catch/throw 구조와비교하면, reject 메소드를자바스크립트의 throw 키워드라고생각하면된다. 그리고에러통지는 throw문대신에 reject 메소드를통해생성된 거부됨 상태를반환하는식으로현재의프라미스에서파생된프라미스에전송한다. promiseb = promisea.then(function(result) { // 성공할경우의코드를여기넣자 - 기능을수행하고 // promiseb를기존값이나새값으로해독할것 return result; }, function(reason) { // 에러할경우의코드를여기넣자 - 가능하면에러를처리하고 // promiseb를 newpromiseorvalue로해독하든지, // promiseb에 ' 거부됨 ' 을전달할것 if (canhandle(reason)) { // 에러를처리하고복원하는코드를여기넣을것 return newpromiseorvalue; } return $q.reject(reason); }); 매개변수 : { 사유 } - 거부사유를대변하는상수, 메시지, 예외, 객체중하나다. 반환값 : { 프라미스 } - 특정사유로거부됨 으로이미해독이완료된프라미 스를반환한다. 들어가기전에필수용어와개념정리 7

24 4. when( 값 ) 메소드값이나 then 메소드가들어있는프라미스로산출될가능성이있는객체를 $q 객체안에캡슐화한다. when 메소드는프라미스가들어있을지없을지불확실한객체를처리할때나프라미스의출처를신뢰할수없을때사용하면적절하다. 매개변수 : { 값 } - 값또는프라미스다. 반환값 : { 프라미스 } - 매개변수로전달한값이나프라미스에대한프라미스 를반환한다. 들어가기전에필수용어와개념정리 8

25 1 서버와통신하기 AngularJS 기초편 ( 한빛미디어, 2013) 에서는 AngularJS 애플리케이션을구성하는방법, AngularJS의다양한부분이서로연동되는원리, AngularJS를사용한템플릿의동작원리를알아보았다. 그내용만으로도세련된애플리케이션을만들수는있지만, 대부분은클라이언트측애플리케이션에그쳤다. AngularJS 활용편 에서는 AngularJS 기초편 에서배운내용을바탕으로확장된기능을배울것이다. AngularJS에대한기초적인지식이없다면, AngularJS 기초편 을꼭학습하길바란다. AngularJS 기초편 에서 $http 서비스를사용한서버측통신에대해서설명했는 데, 이장에서는 $http 서비스를응용해실무애플리케이션을제작하는방법에대 해알아볼것이다. AngularJS를사용해서버와통신하는방법 ( 추상화의최하위계층수준에서그리고래퍼 wrapper 를이용한방법 ) 을알아보자. 또한, AngularJS의내장캐시시스템을사용해애플리케이션의실행속도를높이는방법에대해서도알아볼것이다. SocketIO를사용해 AngularJS 실시간애플리케이션을개발하는방법은지시어로 SocketIO를래핑 ( 캡슐화 ) 하는 4장예제를참고하면되므로, 이장에서는 SocketIO에대해서는다루지않겠다. 1.1 $http 서비스를사용한통신 AJAX 애플리케이션에서 XMLHttpRequest를사용해서버에요청하는일반적인절차는 XMLHttpRequest 객체로핸들을가져와서요청을서버에보내고에러코드를검사한후최종적으로서버에서받은응답을처리하는것이다. 이과정을코드로작성하면다음과같다. 1 장서버와통신하기 9

26 var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readystate == 4 && xmlhttp.status == 200) { var response = xmlhttp.responsetext; } else if (xmlhttp.status == 400) { // 또는 4 계열의어떤상태코드든 // 상관없음. // 여기에적절한에러처리코드를넣자. } }; // 연결설정 xmlhttp.open("get", "http://myserver/api", true); // 요청보내기 xmlhttp.send(); 앞의코드에서보듯이 XMLHttpRequest 를사용한서버요청방식은간단하고일반 적이면서자주반복되는작업임에도작성해야할코드는복잡하다. 반복적으로해야 할작업이라면래퍼를작성하거나라이브러리를사용하는것이적절할때가많다. AngularJS의 XHR API(XMLHttpRequest) 는프라미스 Promise 인터페이스를따른다. XHR은비동기메소드호출이므로서버가언제응답을반환할지정확한날짜와시간을알수없다 ( 즉시반환하기도한다 ). 프라미스인터페이스는서버의응답이처리될방식을확실히보증하므로, 프라미스는예측가능한방식으로사용될수있다. 서버에서사용자정보를가져오는코드를작성해보자. XHR API 가 /api/user 에 서이용가능하고 URL 매개변수로 id 를받는다면, AngularJS 의코어 $http 를사 용한 XHR 요청은다음과같이작성할수있다. 1 장서버와통신하기 10

27 $http.get('api/user', { params : { id : '5' } }).success(function(data, status, headers, config) { // 여기엔성공했을때수행할작업을넣자. }).error(function(data, status, headers, config) { // 여기엔에러처리를넣자. }); jquery 개발경험이있다면 AngularJS 와 jquery 가비동기요청을주고받는방식 이비슷하다는것을알수있을것이다. 앞의예제에사용한 $http.get 메소드는 AngularJS의코어 $http 서비스에들어있는편의메소드 convenience method 중하나다. 마찬가지로, AngularJS를이용해같은 URL 매개변수와 POST 데이터를사용한 POST 요청을하려면다음과같이작성하면된다. var postdata = { text : ' 긴 blob 타입의문자열 ' }; // 다음행이 params를통해 URL에덧붙여져서, // /api/user?id=5로의포스트요청이만들어진다. var config = { params : { id : '5' } }; $http.post('api/user', postdata, config).success( function(data, status, headers, config) { // 여기엔성공시에수행할작업을넣자. }).error(function(data, status, headers, config) { // 여기엔에러처리를넣자. 1 장서버와통신하기 11

28 }); 다음에나열한것외에도일반적인요청타입에는대부분이와비슷한편의메소드 가있다. GET HEAD POST DELETE PUT JSONP 부가요청옵션설정 다음과같은상황이라면내장된표준요청옵션만으로는충분치않다. 요청에권한부여헤더를추가할때 요청에대한캐시처리방식을변경할때 보내는요청이나받는응답을일정한설정방식으로변환할때 위와같은상황에서는환경설정을위한옵션객체를통해요청을설정할수있다. 앞의예제에서는 config 객체를사용해서 URL 옵션매개변수를지정했다. 그런데앞에서살펴본 GET 방식과 POST 방식에도편의메소드가있다. 그러한편의메소드의호출방법은다음과같다. $http(config) 앞의메소드를호출하는기본적인형식을의사코드 pseudo-code 로나타내면다음과 같다. 1 장서버와통신하기 12

29 $http({ method: 문자열, url: 문자열, params: 객체, data: 문자열또는객체, headers: 객체, transformrequest: function transform(data, headersgetter) 또는함수배열, transformresponse: function transform(data, headersgetter) 또는함수배열, cache: 부울값또는캐시객체 timeout: 숫자, withcredentials: 부울값 }); GET과 POST를비롯한편의메소드안에서메소드가설정되므로개발자가직접지정하지않아도된다. config 객체는 $http.get 메소드와 $http.post 메소드에마지막인자로전달되므로편의메소드중어느것을사용해도 config 객체를활용할수있다. 다음과같은키를설정한 config 객체를전달하면이미이뤄진요청을수정할수있다. method GET 이나 POST 같이 HTTP 요청타입을나타내는문자열 url 요청되는리소스의절대 URL 이나상대 URL 을나타내는문자열 params 다음과같이 URL 매개변수로변환될키와값을나타내는문자열-문자열객체 ( 정확히말하면맵 ) [{ 키1: ' 값1', 키2: ' 값2'}] 1 장서버와통신하기 13

30 앞의코드는다음과같이변환되어 URL 뒤에붙는다.? 키 1= 값 1& 키 2= 값 2 값에문자열이나숫자대신객체를사용하면그객체는 JSON 문자열로변환된다. data 요청메시지데이터로써보낼문자열이나객체 timeout 요청이타임아웃처리되기전의밀리초단위의대기시간 다음절에서설정할수있는옵션몇가지를더설명하겠다 HTTP 헤더지정 AngularJS에는보내는모든요청에적용되는기본헤더가있는데, 그중일부는다음과같다. 1. Accept: application/json, text/plain, 2. X-Requested-With: XMLHttpRequest 특수헤더를설정하고싶을땐다음과같은두가지방법을이용하면된다. 보내는모든요청에헤더를적용하는첫번째방법은특수헤더를 AngularJS의기본헤더에포함시키는것이다. 이러한기본헤더는 $httpprovider.defaults. headers 환경설정객체에서설정한다. 그리고설정단계는보통애플리케이션을설정하는 config 부분에서이뤄진다. 따라서모든 GET 요청을대상으로 ' 정보수집금지 (DO NOT TRACK)' 를설정하되 Requested-With 헤더를모든요청에서삭제하려면다음과같이작성하면된다. 1 장서버와통신하기 14

31 angular.module('myapp',[]).config(function($httpprovider) { // AngularJS의기본헤더인 X-Request-With를삭제. delete $httpprovider.default.headers.common['x-requested-with']; // 모든 GET 방식요청을대상으로 'DO NOT TRACK'( 정보수집금지 ) 를지정 $httpprovider.default.headers.get['dnt'] = '1'; }); 특정요청들만을대상으로할때는특수헤더를설정한다. 다만기본헤더를사용하지않으려면특수헤더를 config 객체에포함시켜 $http 서비스로전달하면된다. 개발자정의헤더는다음과같이 URL 매개변수와함께두번째매개변수에넣어 GET 요청에전달한다. $http.get('api/user', { // Authorization 헤더를설정. 실제애플리케이션이라면, // 서비스에서인증토큰을가져와야할것이다. headers: {'Authorization': 'Basic Qzsda231231'}, params: {id: 5} }).success(function() { // 여기엔성공하면수행할코드를넣자. }); 애플리케이션내부에서인증을처리하는방법을보여주는완전한예제는 4 장을참 고하기바란다 응답을캐시에저장 AngularJS에는 HTTP GET 요청에즉시사용할수있는간단한캐시시스템이있다. 이캐시시스템은모든요청에사용되지않게기본으로설정되어있는데, 다음과같이작성하면요청에캐시를사용하도록설정할수있다. 1 장서버와통신하기 15

32 $http.get('http://server/myapi', { cache: true }).success(function() { // 여기엔성공하면처리할작업을넣자. }); 앞의코드처럼캐시를사용하도록설정하면다음과같은장점이있다. 첫째, AngularJS 는서버에서받은응답을캐시에저장해두었다가같은 URL 로 요청을다시보낼때캐시에저장해둔응답을반환한다. 둘째, 캐시는신속하고영리해서동시에한 URL 에여러요청을해도하나의요 청만서버로전송하며, 그요청에대한응답만반환한다. 그러나이런점은사용성관점과상충할수도있다. 사용자입장에선이전의결과가일단표시된후에새로운결과가불쑥표시되기때문이다. 예를들어, 사용자가어떤항목을막클릭하려던찰나에그항목이갑자기변경될수도있다. 캐시가보낸응답이든서버가보낸응답이든비동기적인성질은같으므로, 당연히코드는처음요청을수행했을때와똑같은동작을할수밖에없다 요청변환과응답변환 AngularJS 는 $http 서비스를통해이뤄지는모든요청과응답을대상으로다음과 같은기본적인변환을적용한다. 요청변환 요청된 config 객체의 data 속성에객체가들어있으면, 그객체를 JSON 형식 으로직렬화한다. 1 장서버와통신하기 16

33 응답변환 XSRF 접두어가붙어있으면그접두어를떼어낸다. JSON 응답이감지되면그 응답을 JSON 파서로역직렬화한다. 변환이적용되는것을원치않거나자신의변환을추가하려면 config 객체안에함수를넣어전달하면된다. 이함수는 HTTP 요청 / 응답의내용, 헤더를가져와서직렬화하고수정한후응답한다. 이런 config 함수는 transformrequest 키와 transformresponse 키를사용해설정한다. 두키는모듈의 config 함수에들어있는 $httpprovider 서비스를사용해설정한다. 그렇다면 transformrequest 키와 transformresponse 키는언제사용할까? jquery 작업처리방식에더적합하게설정된서버가있다고하자. 이서버는 POST 요청데이터를 JSON 형식 {key1: val1, key2: val2} 이아니라문자열형식 {key1=val1&key2=val2} 을기대한다. 요청시마다받은문자열을목적에따라수정하거나 transformrequest 호출을개별적으로추가할수도있지만, 필자는보내는모든호출을 JSON 형식에서문자열형식으로변환되도록범용 transformrequest를추가하였다. 작성한코드는다음과같다. var module = angular.module('myapp'); module.config(function($httpprovider) { $httpprovider.defaults.transformrequest = function(data) { // jquery의 param 메소드를이용해서 // JSON 데이터를문자열형식으로변환하자. return $.param(data); }; }); 1 장서버와통신하기 17

34 1.2 단위테스트실시 앞에서 $http 서비스의사용법과다양한설정방법을알아보았다. 그러나단위테 스트를작성하고테스트가원활히돌아가는지확인하려면어떻게해야할까? 계속말하지만 AngularJS 는테스트를염두하고설계된프레임워크이므로, 단위 테스트에서직접올바른요청이이뤄지는지여부를테스트하고응답이처리되는 시점과방식까지도제어할수있는목백엔드 mocked backend 가있다. 그럼, 서버에요청하고데이터를가져와서해당스코프 scope 에서데이터가뷰 view 에특 정한형식으로표시되게하는컨트롤러를어떻게단위테스트하면되는지알아보자. NamesListCtrl 은하나의용도로사용하는아주간단한컨트롤러다. 이용도는필 자가작성한 names API 를호출해서해당스코프에모든이름을저장하는것이다. function NamesListCtrl($scope, $http) { $http.get('http://server/names', {params: {filter: none }}). success(function(data) { $scope.names = data; } ); } 앞의코드를어떻게단위테스트할까? 단위테스트는다음과같은사항이가능해 야한다. NamesListCtrl 는모든종속물을검색해서제대로주입할수있어야한다. NamesListCtrl 는로딩되는즉시서버에요청해서이름을가져와야한다. NamesListCtrl 는받은응답을해당스코프의 names 변수에저장해야한다. 1 장서버와통신하기 18

35 테스트안에컨트롤러를생성하고, 그안에스코프와가짜 HTTP 서비스를주입하는방법도있지만, AngularJS가제품코드에적용하는방식대로테스트를생성하는방법이좋다. 이방법은훨씬복잡하지만사용하는것을권장한다. 그이유는다음코드를보면알수있다 ( 소스코드주석을잘살펴보기바란다 ). describe('nameslistctrl', function(){ var scope, ctrl, mockbackend; // AngularJS가다음을테스트안에주입한다. beforeeach(inject(function(_$httpbackend_, $rootscope, $controller) { // 다음은가상의백엔드이므로, 요청과서버의응답을제어할수있다. mockbackend = _$httpbackend_; // 컨트롤러가생성되면다음호출이시작될것이므로 // 컨트롤러생성에앞서예상형식을지정. mockbackend.expectget('http://server/names?filter=none'). respond(['brad', 'Shyam']); scope = $rootscope.$new(); // AngularJS가제품화코드에적용하는방식대로컨트롤러를생성. ctrl = $controller(phonelistctrl, {$scope: scope}); })); it(' 로딩되면서버에서이름을가져와야함 ', function() { // 초기에이요청은응답을반환하지않았음. expect(scope.names).tobeundefined(); // 현재보내는모든요청에대한응답을반환하라고가상백엔드에게명령 mockbackend.flush(); // 이제 names 를스코프상에서지정해야함. 1 장서버와통신하기 19

36 expect(scope.names).toequal(['brad', 'Shyam']); }); }); 1.3 REST 기반의리소스사용하기 $http 서비스는하위수준으로구현되어있어 XHR 요청을할수있으면서도많은부분을제어할수있고유연성도뛰어나다. 그러나대체로개발자가처리하는것은개인에대한세부속성, 개인객체, 신용카드객체같이특정속성과메소드로캡슐화된객체와객체모델이다. 이렇게수정할수있는것이제한적일경우, 객체모델을파악해서표현하는자바 스크립트객체를작성할수있다면좋지않을까? 상태는서버에그대로유지된채 save 나 update 같은자바스크립트객체의속성들만수정할수있다면어떨까? $resource 를사용하면이러한기능을만들수있다. AngularJS 리소스를사용하 면객체모델을정의할수있으며다음과같은것들을지정할수있다. 리소스의서버측 URL XHR 요청에서주로볼수있는매개변수의타입 객체모델의특정기능 ( 예 : 신용카드의 charge( ) 메소드 ) 과비즈니스로직을캡슐화하는부가메소드 (get, save, query, remove, delete 메소드는기본적으로들어있다 ) 응답의예상타입 ( 배열또는객체 ) 헤더 1 장서버와통신하기 20

37 Angular 리소스는어떤경우에사용하나? Angular 리소스는서버측이 REST 방식으로동작할때만사용해야한다. 이장의끝부분에서살펴볼신용카드예제는다음과같은특징이있다. 1. /user/123/card로 GET 요청을보내면 123이라는사용자의신용카드목록이반환된다. 2. /user/123/card/15로 GET 요청을보내면 123이라는사용자의 ID가 15인신용카드가반환된다. 3. POST 데이터안의신용카드정보와함께 /user/123/card로 POST 요청을보내면 123 사용자의새신용카드가생성된다. 4. POST 데이터안의신용카드정보와함께 /user/123/card/15로 POST 요청을보내면 123 사용자의 ID가 15인신용카드의정보가보낸정보로업데이트된다. 5. /user/123/card/15로 DELETE 요청을보내면 123 사용자의 ID가 15인신용카드가삭제된다. 개발자의필요에따라서버에질의할수있는객체를제공할뿐만아니라, $resource 를사용하면반환객체를가지고마치영구저장된데이터모델처럼작업하고, 수 정하고, 영구저장되게요청할수도있다. ngresource 는별도의옵션모듈이다. 따라서 ngresource 모듈을사용하려면다 음과같이해야한다. 작업하는기본자바스크립트파일안에 angular-resource.js 파일을인클루딩한다. 모듈종속물선언코드안에 angular.module('mymodule', ['ngresource']) 와같은식으로 ngresource를포함시킨다. 필요하면 inject $resource를사용한다. ngresource 메소드를사용해리소스를생성하는방법을살펴보기전에, 기본 $http 서비스를사용하여리소스를생성하는방법부터알아보자. 신용카드예제의리소 1 장서버와통신하기 21

38 스는신용카드를가져오고질의, 저장할수있어야하며신용카드결제도가능해야 한다. 이러한기능을구현하는첫번째방법은다음과같다. myappmodule.factory('creditcard', [ '$http', function($http) { var baseurl = '/user/123/card'; return { get : function(cardid) { return $http.get(baseurl + '/' + cardid); }, save : function(card) { var url = card.id? baseurl + '/' + card.id : baseurl; return $http.post(url, card); }; } ]); }, query : function() { return $http.get(baseurl); }, charge : function(card) { return $http.post(baseurl + '/' + card.id, card, { params : { charge : true } }); } 아니면, 두번째방법으로다음과같이애플리케이션전반에리소스를반영하는 Angular 서비스를간단히작성할수도있다. 1 장서버와통신하기 22

39 myappmodule.factory('creditcard', ['$resource', function($resource) { return $resource('/user/:userid/card/:cardid', {userid: 123, cardid: {charge: {method:'post', params:{charge:true}, isarray:false}); }]); 이제 AngularJS 주입객체에 CreditCard를요청할때마다 Angular 리소스를가져오는데, 이리소스엔기능이시작되는메소드몇개가기본으로들어있다. Angular 리소스에포함된각메소드가무엇이고어떤방식으로동작하는지표 1-1로정리했다. 이것을참고하면서버를어떻게설정해야할지알수있다. 표 1-1 신용카드리소스 리소스함수방식 URL 예상반환타입 CreditCard.get({id: 11}) GET /user/123/card/11 Single JSON CreditCard.save({}, ccard) POST /user/123/card (post 데이터 ccard와함께 ) Single JSON CreditCard.save({id: 11}, ccard) POST /user/123/card/11 (post 데이터 ccard와함께 ) Single JSON CreditCard.query( ) GET /user/123/card JSON Array CreditCard.remove({id: 11}) DELETE /user/123/card/11 Single JSON CreditCard.delete({id: 11}) DELETE /user/123/card/11 Single JSON 좀더확실한이해를위해신용카드예제를보자. // CreditCard 서비스가여기에주입된다고가정하자. // GET 요청을보내는서버 /user/123/card 에서컬렉션을가져올수있다. 1 장서버와통신하기 23

40 var cards = CreditCard.query(); // 하나의카드를가져와서콜백함수에서도사용할수있다. CreditCard.get({ cardid : 456 }, function(card) { // 각항목은 CreditCard의인스턴스다. expect(card instanceof CreditCard).toEqual(true); card.name = "J. Smith"; // GET 이외의방식이그인스턴스에매핑된다. card.$save(); // 개발자정의메소드도매핑된다. card.$charge({ amount : 9.99 }); // 데이터 {id:456, number:'1234', name:'j. Smith'} 와함께 // POST 요청을 /user/123/card/456?amount=9.99&charge=true로보낸다. }); 기능의대부분이앞의예제에들어있으므로, 중요한부분만하나씩파헤쳐보자 선언 $resource를선언하려면그냥적절한매개변수를전달하면서주입된 $resource 함수를호출하면된다. 어떻게주입해야할지모른다면 AngularJS 기초편 을읽어보기바란다. $resource 함수는필수인자 1 개와옵션인자 2 개를받는다. 필수인자는리소스의 URL 이고, 옵션인자는기본매개변수와리소스에설정하고자하는부가동작이다. URL 매개변수는매개변수형태로표기돼있다. 앞에붙은콜론 (:) 은매개변수임을나타낸다. 즉, :userid는 userid 매개변수의텍스트로대체되며, :cardid는 cardid 매개변수의값으로대체된다. URL 매개변수가전달되지않으면 URL 매개변수는빈문자열로대체된다. 1 장서버와통신하기 24

41 두번째매개변수는각요청과함께전달될기본매개변수를처리한다. 이예제에선 userid에상수 123을전달했다. cardid 매개변수는더욱중요하다. 이것은서버에서반환된객체를사용하고그객체의 $save 같은메소드를호출하면 cardid 필드가객체의 id 속성에서선택될것임을나타낸다. 세번째인자는개발자정의리소스를공개할각종메소드다. 이에대해서는다음 절에서자세히알아볼것이다 개발자정의메소드 $resource 를호출할때전달할세번째인자는리소스에공개하고자하는부가메 소드들이다. 신용카드예제는세번째인자로 charge 메소드를전달했다. charge 메소드는객체안에서공개할메소드명을키로사용해서설정하면된다. 이설정을위해서는 GET이나 POST 같은요청방식, 요청안에포함시켜전달해야하는매개변수 ( 신용카드예제에선 charge=true), 반환된결과가배열인지아닌지여부 ( 신용카드예제에선배열이아님 ) 를지정해야한다. 모두지정했으면 CreditCard. charge( ) 함수를언제든호출해도된다. 실생활에서는사용자가결제할때마다호출될것이다 콜백은꼭필요할때만사용하자세번째로중요한것은리소스호출의반환타입이다. CreditCard.query( ) 호출을다시살펴보자. 콜백안에신용카드를지정하지않고신용카드의변수를직접지정했음을알수있다. 비동기서버요청으로그코드가과연돌아가기는할까? 코드가돌아가는지아닌지를걱정하는것은당연하지만, 코드는실제로정확하고, 잘돌아간다. CreditCard.query( ) 호출코드에서 AngularJS 는참조 ( 예상반환 1 장서버와통신하기 25

42 타입에따라객체일수도있고배열일수도있음 ) 를할당했는데, 이참조는나중에 서버요청이반환될때내용이할당된다. 그때까지객체는빈상태로유지된다. AngularJS 애플리케이션에서가장일반적인흐름은서버에서데이터를가져와서변수에할당하고그변수를템플릿에표시하는것이므로, 이러한단축방법도괜찮다. 컨트롤러코드에서는단지서버를호출하고적절한스코프변수에반환값을할당하여값이반환되면템플릿이그값을표시하게하면된다. 이방법은원하는비즈니스로직이반환값을이용해실행되는경우에는통하지않 는다. 이럴때는 CreditCard.get( ) 함수안에서쓰이는콜백을이용해야한다 간소화된서버측작업 단축반환타입과콜백중어느것을사용하든지반환객체에대해알아야할몇가 지가있다. 반환값은기존의평범한자바스크립트객체가아니라사실상 resource 타입의객체다. 그래서반환값에는서버가반환하는값뿐만아니라 $save( ) 와 $charge( ) 같은부수적인기능도들어있다. 결과적으로데이터가져오기, 수정하기, 서버에수정내용저장하기 (CRUD 애플리케이션의가장일반적인기능 ) 같은서버측기능을쉽게수행할수있다 ngresource 단위테스트하기 ngresource는캡슐화모듈이며, $http 코어 AngularJS를기반으로한다. 따라서단위테스트방법도같다. AngularJS 기초편 의 $http 서비스에서살펴봤던단위테스트예제와방법이거의다르지않다. 단지리소스에의해이뤄지리라예상되는최종요청만파악해서가상의 $http 서비스에전달하면되고, 나머지는전부똑같다. 앞의코드를대상으로한테스트는다음과같다. 1 장서버와통신하기 26

43 describe(' 신용카드리소스 ', function() { var scope, ctrl, mockbackend; beforeeach(inject(function(_$httpbackend_, $rootscope, $controller) { mockbackend = _$httpbackend_; scope = $rootscope.$new(); // CreditCard 리소스가컨트롤러에사용된다고전제함. ctrl = $controller(creditcardctrl, { $scope : scope }); })); it(' 신용카드목록을가져왔어야함 ', function() { // CreditCard.query() 에대한예상호출방식을지정. mockbackend.expectget('/user/123/card').respond([ { id : '234', number : ' ' } ]); ctrl.fetchallcards(); // 처음에이요청은응답을반환하지않았다. expect(scope.cards).tobeundefined(); // 가상의백엔드에게현재보내는모든요청에대한응답을반환하라고 // 명령함. mockbackend.flush(); // 이제신용카드의스코프를설정함. expect(scope.cards).toequaldata([ { id : '234', 1 장서버와통신하기 27

44 number : ' ' } ]); }); }); 이테스트는간단한 $http 단위테스트와매우유사한데사소한차이가하나있다. expect 함수에서간단한 equals 메소드를사용하지않고특수한 toequaldata 호출을사용했다는점이다. expect 함수는기민해서 ngresource가객체에추가하는부가메소드를무시한다. 1.4 $q 와프라미스 이제까지 AngularJS가비동기지연 API를어떤식으로구현하는지살펴봤다. AngularJS가소속된 API를구성하는방식은프라미스 Promise 제안문서를바탕으로한다. 프라미스제안문서에는기본적으로비동기요청에대해다음과같은사항을준수하도록기술되어있다. 비동기요청은반환값대신프라미스를반환해야한다. 프라미스에는 then 함수가있어야한다. 이 then 함수는두개의인자를받는다. 첫번째인자는 resolved( 해독됨 ) 이벤트나 success( 성공 ) 이벤트를처리하는함수고, 두번째인자는 rejected( 거부됨 ) 이벤트나 failure( 실패 ) 이벤트를처리하는함수다. 두함수는결과나거절사유와함께호출된다. 결과가해독되는순간바로앞에서말한두개의콜백함수중하나가반드시호출돼야한다. 대개지연 q 객체를구현한코드는앞의제안문서에기술된방식을따르지만, AngularJS 의 $q 함수구현코드는다음과같은이유로다르다. $q 객체는 AngularJS 를완벽히인식하므로스코프모델과연동된다. 따라서 1 장서버와통신하기 28

45 결과를더욱빠르게전달할수있고 UI의깜빡임과업데이트를줄일수있다. AngularJS 템플릿역시 $q 객체의프라미스를인식하므로, 템플릿은결과값을통지받을프라미스대신에템플릿스스로를결과값처럼처리할수있다. AngularJS에는흔히수행되는비동기작업들에필요한기본적이고가장중요한기능만구현되어있어메모리공간을적게차지하는편이다. 그런데이렇게까지해서대체뭘하려는것일까? 비동기함수를사용하다가맞닥 뜨릴수있는다음과같은일반적인문제를하나살펴보자. fetchuser(function(user) { fetchuserpermissions(user, function(permissions) { fetchuserlistdata(user, permissions, function(list) { // 여기엔표시할데이터리스트로기능을수행하는코드가들어감. }); }); }); 앞의코드는자바스크립트로코딩할때개발자들이처하게되는 절망의피라미드 pyramid of doom 패턴 01 이다. 반환값의비동기적특성이프로그램의동기적요구와대치하다가결국여러함수가포함되는구조로, 현재문맥을파악하기가훨씬힘들어진다. 게다가에러처리에도문제가있다. 에러를처리하는최선의방법은무엇일까? 각단계에서에러를처리할것인가? 그러면지저분해지기까지한다. 이러한앞의코드의문제점을바로잡으려면프라미스제안문서에나온 then 메소드개념을활용해야한다. then 메소드는성공할경우실행할함수와에러가날경우실행할함수를받는데, 두함수를체인화할수있다. 앞의예제에프라미스 API를사용하면다음과같이평탄화 ( 포함구조로겹쳐진함수들을평이한코드로만드는것 ) 할 01 안티패턴중하나로, 계속되는포함관계로코드가복잡해져서개발자에게절망감을준다고하여붙여진이름이다. 1 장서버와통신하기 29

46 수있다. var deferred = $q.defer(); var fetchuser = function() { // 비동기호출후에응답값을전달하면서 deferred.resolve를호출함. deferred.resolve(user); } // 에러가날경우다음을호출함. deferred.reject(' 실패사유 '); // 마찬가지로, fetchuserpermissions과 fetchuserlistdata가처리됨. deferred.promise.then(fetchuser).then(fetchuserpermissions).then(fetchuserlistdata).then(function(list) { // 여기엔데이터리스트를사용한기능수행코드를넣자. }, function(errorreason) { // 어느단계에든에러가있으면한번에처리하는코드를여기에넣자. } ); 앞의코드에서는절망의피라미드문제가해결됐고, 체인화 02 를위한스코프도생겼으며, 에러처리도한군데서이뤄진다. AngularJS의 $q 서비스를인클루딩해서비동기호출을처리하려면이코드를애플리케이션에사용하면된다. 게다가이방식으로작성하면응답을가로채는것도가능하다. 02 마침표로여러함수를일렬로이어붙여, 관련함수들을실행하는방법이다. 1 장서버와통신하기 30