개발하며 배우는 Polymer (2/2)

Jason Park
19 min readDec 7, 2014

--

Disclaimer

이 예제는 Polymer v0.8을 사용합니다. Polymer v1.0이 정식으로 출시되며 API가 크게 바뀌었음으로 더 이상 유효하지 않습니다.

About

Polymer/Firebase를 이용하여 간단한 게시판을 만드는 과정을 설명하며 Polymer에 대한 이해를 돕기위해 쓴 글이다.

What is Firebase?

Polymer는 1장에서 설명했으니 게시판에 들어갈 custom element을 만들기에 앞서 Firebase가 무엇인지 짧게나마 알아보자. Firebase란?

Firebase는 이렇게 자신들의 서비스를 설명하고 있다.

A powerful API to store and sync data in realtime.

Data관리 API만 제공 해줘도 충분한데 realtime 이라니! 게다가 인터넷 커넥션이 끊긴 상태에서도 오프라인 모드로 작동하고 다시 인터넷에 연결되면 알아서 data sync까지 해준다고 하니..

어머 이건 써야만 해!

Understanding Firebase

Firebase에서 무료로 hacker plan 계정(=개발자 계정)을 만들수 있다. 그리고 app을 만들고 나면 app ID가 생성되는데 이것만 있으면 바로 API 이용이 가능하다. 이걸로 Firebase 사용준비는 끝! 사용방법을 알아보자.

Data Structure

Dashboard를 통해 실시간으로 data를 보고 생성/수정/삭제까지 할 수 있다.

Firebase에선 모든 Data가 JSON 포맷의 key/value 식으로 저장되고 불려진다. 딱히 JSON이라고 하지 않는 이유는 Array도 key/value식으로 변환되어 저장되기 때문이다. 그러니까 이게 무슨 말이냐면 API를 통해

[‘a’, ‘b’, ‘c’, ‘d’, ‘e’]

를 저장하면 Firebase에는

{0: ‘a’, 1: ‘b’, 2: ‘c’, 3: ‘d’, 4: ‘e’}

로 저장된다는 뜻. Firebase는 왜 이런 방법을 택했을까? 이에 대한 설명은 Firebase Blog에서.

그래서 Firebase에선 모든 data는 key를 통해 불려지도록 만들어 두었고 꼭 (key만이 아니더라도) priority라는 값을 통해 data를 정렬할 수 있는 방법을 마련해 두었다. 이에 관해선 공식 문서로 대체하겠다.

Firebase를 사용하는 실제 사례들을 보면 Firebase가 어떻게 쓰이는지 보면 동적인 업무보다는 실시간으로 data 를 관리하고 제공 해주는데 집중적으로 사용됨을 알수있다. Data가 실시간으로 제공되다 보니 여러명이 한 페이지에서 공동으로 동시에 작업하는 경우 Firebase가 많이 쓰이는듯 하다. 예를 들면 구글 docs처럼 한페이지에서 여럿이 동시에 공동작업 하는 경우 또는 더 심플하겐 chat app 등.

Firebase REST API

Firebase에서 제공하는 REST API를 이용하려면 아래와 같이 사용하면 된다. Firebase의 REST API 에 관해선 API 문서를 통해 배우도록 해보자 ☺

코드는 대략 이런 느낌? 사용방법이 굉장히 간단하다.

<script src=”https://cdn.firebase.com/js/client/2.0.5/firebase.js"></script>//CREATE A FIREBASE
var fb = new Firebase(“https://<your-firebase>.firebaseio.com”);
//SAVE DATA
fb.set({ name: “Alex Wolfe” });
//LISTEN FOR REALTIME CHANGES
fb.on(“value”, function(data) {
var name = data.val() ? data.val().name : “”;
alert(“My name is “ + name);
});

Polymer + Firebase

Polymer에서 제공하는 Firebase REST API의 wrapper인 firebase-element를 이용한다면 Polymer 어플리케이션 안에서 Firebase API 직접적으로 이용해야할 경우는 많이 없다 (물론 이해는 하고 있어야 겠지만).

Polymer를 위한 Firebase-Element

Firebase-element는 Polymer element와 Firebase API간의 연계를 정말 손쉽게 만들어준다. 예제를 보며 설명해보자.

<firebase-element id=”base” location=”https://YOUR.firebaseio.com/" data=”{{data}}” ref="{{fb}}" keys=”{{keys}}” dataReady="{{dataReady}}"></firebase-element>

Firebase element를 설정할땐 위와 같이 data의 URL만 location에 제공해줘도 되고 Firebase 인스턴스를 이미 가지고 있는 경우라면 ref 에 넘겨주어도 된다. Location이 이 제공되었다면 ref (Firebase 인스턴스)를 자체적으로 생성한다. 이미 Firebase 인스턴스를 가지고 있는 경우에는 이를 제공해도 된다.

location ref limit start end 값이 변하게 되면 해당 location의 값을 새로 받아온다. limit start end는 query에 제한을 거는데 사용되며 이에 대해선 문서를 참고하시길.

ref 또는 location 이 설정된 firebase-element는 Firebase에서 data를 받아온다. Firebase에서 Data를 불러오는 동안 dataReady 값은 false — 그리고 data가 불려져서 data/key가 생성되면 dataReady 값은 는 true로 변한다. 이를 이용해 data가 불려지기 전까진 더 나은 사용자 경험을 제공하기 위해 data를 불러오는 중이라는 메세지를 보여주는 것이 중요할듯.

Firebase-element의 공식 문서를 통해 Firebase-Element가 제공하는 attributes와 event를 볼수있다. 하지만 문서에 나오지 않는 attributes도 있으니 firebase-element 에 대해 더 자세히 이해하기 원한다면 친절히 주석도 달려있고 코드도 굉장히 깔끔하고 굉장히 이해하기 쉽게 짜여져 있는 소스 코드 보는걸 추천한다.

사용자 관리를 위한 Firebase-Login

Firebase는 사용자 관리도 함께 제공한다. 가입/로그인/사용자 정보 관리는 firebase가 제공하는 User Login & Authentication에서 Auth provider 설정만 해놓으면 Firebase API를 통해 사용이 가능하다. 다른말로 설명하면 Facebook/Google등 Auth provider를 이용해 사용자 로그인을 관리하는게 가능하다는 이야기.

이를 활성화 하기 위해선 Firebase app의 dashboard에선 아래와 같이 Auth provider의 ID/Secret Key를 설정해 주고 활성화 시키면 된다.

Auth Provider마다 3rd party login 설정이 다르니 Dashboard > Login & Auth 페이지에서 원하는 provider 선택 후 ‘Learn More’ 링크를 따라가 설명대로 app을 만들고 설정하자.

Polymer가 제공하는 Firebase-Element와 같이 제공되는 Firebase-Login element는 Firebase를 통해 로그인한 사용자의 정보를 접근하기 쉽게 도와준다.

로그인/로그아웃은 API를 사용해야 하지만 로그인이 된 이후에는 user 객체를 만들고 결과에 따라 login/logout/error 이벤트를 뿌린다. Firebase-Login의 attributes와 이벤트에 대한 설명 그리고 데모는 이곳에서 확인할수 있다.

Talk is cheap! Gimme the code!

자.. 이정도면 준비는 끝났다. 이제 본격적으로 Realtime Web App을 만들어보자!

1장에서 polymer-generator를 통해 만든 프로젝트를 토대로 만들어 보도록 하겠다.

Application 구조

Polymer의 다양한 면을 보여주기 위해 시작한 프로젝트인 만큼 성질이 다른 2개의 게시판을 만들어 보자. 이를 위해 아래 element들을 만들기로 했다.

  • 관리자가 올린 뉴스를 누구나 읽을수 있는 뉴스 게시판을 위한 my-news-board
  • 로그인한 사용자라면 누구나 글쓰기가 가능한 토론 게시판 my-discussion-board

그리고 이들을 위한 하위 elements:

  • 두 게시판 모두 Firebase에서 data를 받으니 같은 성질을 공유할 것이 분명. 게시판의 base element가 될 my-firebase-board
  • 개별 뉴스 아이템을 위한 template인 my-board-card
  • 개별 토론 아이템을 위한 template인 my-discussion-item
  • 토론글을 작성하기 위한 my-discussion-input
  • 로그인한 유저의 상태를 관리하기 위한 my-auth
  • global 변수를 관리하기 위한 my-globals
  • Font-Awesome을 사용하기 위한 my-font-awesome

위 element들을 아래와 같은 구조로 연결해서 게시판을 만들어 보자.

my-app
|- my-layout
|- my-auth
|- my-news-board : my-firebase-board
|- my-board-card
|- my-discussion-board : my-firebase-board
|- my-discussion-input
|- my-discussion-item

my-globals와 my-font-awesome은 어디서든 필요에 의해 쓰여질수 있음으로 위에 포함하지 않았다.

Custom element 만들기

Font-awesome을 사용하기 위한 my-font-awesome

Font-awesome은 web app개발에 자주 쓰이는 이미지를 아이콘화 하여 제공하는 CSS 라이브러리다. Bootstrap의 Glyphicons와 같은 종류의 라이브러리라고 설명하면 이해가 쉬울듯. Glyphicons보다 더 다양한 표현이 가능해 개인적으로 font-awesome을 이용했을뿐 다른 기술적인 이유는 없다.

보통 font-awesome을 이용하기 위해선 HTML의 head 섹션에 font-awesome의 CDN 링크를 걸어두는 것만으로도 사용이 가능하다.

하지만 web component의 css query는 보통의 css query와 다르게 행동한다고 1장에서 언급했던 것을 기억 하는가? 이는 web component는 모듈화 되어 공유되고 사용되기 때문에 web component가 가진 내용은 철저히 관리된 범위 (scope) 안에서 통제 되어야 하기 때문이다.

그러한 이유로 head 섹션에 등록된 font-awesome의 CSS query가 web component 내부까지 흘러 내려가질 못하는 것. 이를 위해선 :shadow, :content, /deep/등 하위 web component까지 적용되는 CSS query가 적용되어야 하지만 이런 query selector는 제공되지 않고 있음으로 하위 element에선 font-awesome을 인식하지 못하는 문제가 발생한다

이해를 위해 sample 코드를 통해 설명해보겠다.

위의 코드를 실행시키면 왼쪽과 같은 결과물이 출력된다.

my-wrapper에서 불러온 font-awesome의 css query는 는 my-wrapper 안에선 문제없이 불려진다 (하트 아이콘).

하지만 같은 방법으로 다른 element (my-fa-wrapper) 에서 똑같은 내용을 출력함에도 불구하고 my-wrapper안에 등록된 CSS query가 하위 element에 영향을 주지 못하기 때문에 my-fa-wrapper는 font-awesome의 스타일을 불러오지 못한다.

같은 이유로 my-wrapper의 상위 element에서도 font-awesome의 스타일에 대한 접근권한이 없음으로 이곳에서 선언된 DOM 또한 my-wrapper의 content로 넘겨진다 하더라도 font-awesome의 스타일을 불러오지 못한다.

Codepen

이를 위해 어느곳에서나 font-awesome 아이콘을 불러올수 있도록 my-awesome-font라는 element를 my-wrapper와 같은 형식으로 만들어 두었다.

my-globals

지금까지의 모든 예제는 Polymer element이 가진 변수의 등록 그리고 사용에 대한 글이었다면 이번엔 글로벌 변수의 사용에 대해서 알아보자.

Javascript 에서 글로벌이란 그냥 window object에 때려박으면 되는거 아니야? 라고 생각할지도 모르지만 이건 안티패턴이니.. 이 방법 말고 Polymer안에서 통제된 글로벌 변수의 사용을 도와줄 my-globals라는 template이 없는 Polymer element를 만들기로 했다. my-globals의 목적은 Firebase app의 URL 그리고 로그인한 사용자의 정보를 이곳에서 관리하려 함이다.

이를 위해서 MonoState 패턴을 사용할 수 있다. Polymer element를 정의할때 클로져 안에 해당 element의 인스턴스가 공유할 변수를 선언하고 인스턴스가 생성 되었을때마다 ready callback을 이용해 속성을 부여하는 방법이다.

이 패턴을 이용하여 여러 my-globals는 변수의 값을 공유할수 있게 되었다.

로그인한 사용자 관리를 위한 my-auth

my-auth는 로그인/로그아웃 프로세스 관리를 맡는다.

인스턴스가 생성되면 ready callback을 통해 my-globals의 사용자 정보를 이곳에서 설정하고 로그인/로그아웃시 같은 방법으로 my-globals를 업데이트 하고 이에 관한 이벤트를 core-signals로 뿌려서 사용자 상태변화를 다른 element에게 알린다.

이는 DOM 상에 ID를 가지고 있는 element가 있다면 this.$ 를 통해 손쉽게 불러올수 있다. ID가 없는 element를 부르고 싶을땐 querySelector를 사용해야 한다.

Polymer가 제공하는 dialog/button을 이용해 손쉽게 로그인/로그아웃 레이아웃을 짰으며 이들의 클릭 이벤트를 Firebase의 로그인/로그아웃 API와 연결시켜 놓았다.

토론글을 작성하기 위한 my-discussion-input

사용자가 로그인 상태가 아닐 경우 로그인을 유도하는 메세지를 남겨놓고 로그인 상태일 경우 글을 등록할수 있도록 하는 심플한 컨셉의 element다.

이 element에선 my-globals에 등록되어 있는 사용자 정보를 참고하여 사용자 상태를 확인하고 my-auth가 뿌리는 core-signal의 user-changed 이벤트를 구독하여 상태 변화에 반응하도록 짜여져 있다.

이 element에서 특이한 부분이라면 core-ally-keys를 사용하여 사용자가 텍스트 필드 상에서 엔터키를 누르면 반응하도록 되어 있다는 점? 이 core-element의 사용 방법에 대해선 이곳에서 자세하게 알수 있다.

키보드 이벤트에 선택적으로 반응하는게 HTML 한줄이면 가능!

토론글의 내용을 나타내기 위한 my-discussion-item

이 Polymer element에서 앞서 다루지 않은 부분은 26번째 줄에서 사용된 Polymer.mixin이다.

Polymer.mixin은 한개 이상의 객채의 모든 속성을 제공된 객채에 복제하는 Polymer가 제공하는 helper 중 하나로 같은 속성이 여러 element에서 반복적으로 선언될때 이들이 공유하는 속성을 지닌 객채 (또는 객채들) 를 만들고 Polymer element 등록시 Polymer.mixin을 사용하여 같은 코드가 중복됨을 피할수 있다.

Polymer.mixin(target, obj1 [, obj2, ..., objN ] )

10번째 줄을 보면 현 코드에는 없지만 sharedMixin 이란 이름의 객채에 등록된 ticksToDateString 이라는 함수가 사용됨을 알수 있다. 이 함수는 my-board-card 에서도 사용되기에 코드 중복을 피하고자 mixin을 사용했다.

window.sharedMixin = {
ticksToDateString: function (ticks) {
var date = new Date(parseInt(ticks));
return date.toLocaleDateString();
}
};

뉴스 아이템의 내용을 위한 template인 my-board-card

my-board-card는 title, author, content라는 속성을 가진 객채를 받아 이 내용을 템플레이트에 적용하는 역활을 한다.

이 element에선 지금까지와는 다르게 change watcher를 쓰지 않고 Polymer element등록시 observe를 item.content에 적용하는 것을 알수 있다.

Change watcher는 prototype의 가장 상위 레벨의 data에 대한 변화만 감지하고 반응하게 설계되어 있기때문에 prototype이 품고있는 객채의 속성변화까지 감지하진 못한다. 이들의 변화에 개별적으로 반응하기 위해선 observe에 이들 속성을 등록하여 observe block이 감시하도록 해야 한다.

게시판의 base element가 될 my-firebase-board

Polymer는 element를 등록할때 prototype을 사용하고 하니.. 뭐 당연히 상속 뭐 이런 개념이 있지 않을까? 라고 생각 해본적 있다면 답은 Yes다.

polymer-element의 extends라는 attribute에 상속에 parent element의 이름을 부여함으로 부모의 (template를 포함한) 모든 성질을 접근할수 있게 된다.

아래 코드를 예제로 설명을 해보자.

polymer-cooler는 polymer-cool을 자신의 parent element로 설정해놓았음으로 polymer-cool의 praise 변수에 별도의 설정없이 접근이 가능하다.

그리고 자신의 template안에 parent element의 template를 출력하기 위해선 <shadow></shadow>를 사용한다.

이를 토대로 게시판의 정보를 firebase로부터 읽어드리고 data를 불러들이는 동안 페이지에 로딩중임을 사용자에게 보여줄 정도만 담당하는 element를 만들어 보았다.

로그인한 사용자라면 누구나 글쓰기가 가능한 토론 게시판 my-discussion-board

토론 게시판 element는 위에서 만든 elemente를 조합하여 아래와 같이 만들수 있다.

관리자가 올린 뉴스를 누구나 읽을수 있는 뉴스 게시판을 위한 my-news-board

뉴스 게시판 element도 마찬가지로 위에서 만들어 둔 element를 연결해 주면 된다.

페이지 레이아웃을 짜기 위한 my-layout

1장에서 만들어 둔 my-layout에 my-auth를 더해 외부에선 게시판의 내용만 관리하면 될수 있게 만들어 보았다.

그리고 core-localstorage를 이용해 사용자가 마지막에 방문했던 게시판의 정보를 브라우저의 localstorage에 저장하고 사용자가 돌아왔을때 다시 같은 게시판에서 시작할수 있도록 설정해 주었다.

Local Storage란? 브라우저 내에 key/value값을 유효기간 없이 저장해 주는 HTML5 API다.

레이아웃과 게시판 element를 연결해 줄 my-app

이제 마지막 element다. my-app은 my-layout과 2개의 개시판 element를 연결하는 역활을 한다.

Firebase로 배포하기

1장에서 써본 grunt serve로 웹어플리케이션을 실행시켜 보았고 무리없이 작동 한다면 이제 코드를 배포할 차례다. 웹 어플리케이션이면 인터넷으로 접속이 되야지!

Node를 사용했던 이유는 파일들을 HTTP 서버에서 호스팅 하기 위함이었으나 호스팅 서버에 같은 파일들을 배포할 목적이라면 Node 관련 코드의 배포는 필요하지 않다.

이를 위해 polymer-generator가 미리 제공해 놓은 build 모드를 사용해 보자.

> grunt build

build를 실행시키면 배포에 필요한 파일들만 모아서 dist라는 새 폴더에 복사한다.

Firebase는 static 파일들 호스팅도 해주기 때문에 Firebase 매뉴얼를 따라서 배포 해보자.

아래 명령어를 실행시켜 firebase의 CLI를 설치하고

> npm install -g firebase-tools

dist 폴더 안에서 아래 명령어를 실행시키면 곧바로 배포되고 바로 사이트에 접속이 가능하다.

> firebase init
> firebase deploy

마치며..

Polymer를 사용해 개발한 후 느낀것은 (웹프래임워크의 기준이 된듯한) AngularJS나 다른 framework보다 훨씬 직관적이고 적응이 쉽단 것이었다.

레고 블록 쌓듯 element 하나하나 쌓고 연결시키며 다른 framework와 비교해 훨씬 적은 양의 coding만으로도 같은 기능을 구현하기 쉬웠고 element간 data binding은 관해서 거의 생각이 필요없을 정도로 간단했다.

AngularJS 2.0의 변화가 web component와 굉장히 닮아있었다고 느꼈었는데 Polymer를 통해 web component를 접해보니 AngularJS의 변화 또한 옳은 방향의 변화라고 느껴졌다. (으.. 그놈에 scope!)

개발자들이 Polymer와 web component를 좀 더 쉽고 편하게 접하게 되는 계기가 됐기를 바라며 이 글을 마친다.

--

--