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

Publication

Category

Recent Post

2016. 1. 11. 07:58 Angular/Prototyping





앞으로의 Web App 개발은 Component기반 개발 방식으로 진행될 가능성이 높다. 아니 그렇게 될 것으로 보인다. 왜냐하면 표준 제정 기구와 기업이 원하고 그안에서 일하는 개발자들이 원한다. 여기서 기구는 W3C에서 스펙작업을 하고 있고, 기업은 구글형님이 열심히 삽질하며 진행을 하고 있다. 역시 삽질 내공은 구글이 참 잘 하는것 같다. 돈이 많아서 그런다기 보다는 미래의 먹거리를 위해 열심히 뛰는 양상이다. 


애플은 여기서 한발 물러나 있다. 그들의 주 수입은 하드웨어판매와 플랫폼에서 발생하는데, 여기서 말하는 플랫폼은 iOS 장터에서 거래되는 대부분의 수익이 Native App에서 발생한다. 따라서 굳이 Web에 열심히 힘쏫을 필요가 없다는 이야기다. 애플이 그냥 조금씩 서두리지 않고 곁눈질만 하는 놈이라면 구글은 광고로 돈을 벌고 그 광고는 웹에서 발생하니 웹의 외연 확장을 위해 Web Components는 중요한 기술로 생각한다. 그래서 열심히 3년전부터 구글 I/O를 통해 Polymer의 진행상황을 공유하고 있고, 꾸준히 삽질해 주면서 업계를 리딩할 것으로 보인다. 



"Web Components는 웹상(DOM/CSS 렌더링 엔진이 탑재된 모든 것)에서 Native Element와 같은 지위를 얻어 컴포넌트 기반 개발을 가능케 하는 핵심 스펙이다." 



라고 나는 생각한다. 기존 브라우저에서는 기본 정의된 Tag들만 해석을 할 수 있지만, 얼마든지 내가 만든 컴포넌트가 브라우저가 Native하게 해석을 해준다면... 그러니 Native 엘러먼트가 되면 Angular, Ember, React, Knockout 같은 프레임워크나 라이브러리 컴포넌트와 자연스럽게 붙여서 상호작용토록 사용할 수 있지 않겠는가? 앵귤러를 예로 들면 AngularJS v1.* 에서 Web Components와 엮어서 써볼려는 눈물겨운 노력도 있었지만 말 그대로 그냥 시도라고 보자. 근데 이상한 것은 Angular 도 구글! Web Components의 Polyfill 라이브러리인 Polymer도 구글! Future Web App Developments 방향이 Component based Development이니 Angular 프레임워크도 이 방향으로 새롭게 갱생할 필요를 느꼈을 것이다. 그래서 2년전부터 열심히 개발중인 Angular v2.* 에서 Web Components와 함께 쓸 수 있는 방향을 정의했다. 


무엇이라고 이야기하는지 보자.

allow Angular to consume any Web Components without resorting to some Angular unique APIs and or conventions. (Proposal for other frameworks require that component be aware of framework specific API )

  웹 컴포넌트가 네이티브로 인지 되면서 자연스럽게 Angular가 웹 컴포넌트를 소비할 수 있을 것이다. 이를 위해 Angular가 장치를 마련해 두려고 노력을 하고 있고, Misko가 컨퍼런스에서 이야기 하고 있다. 


- allow Angular components to be packaged as Web Components and be used in other frameworks or in vanilla JavaScript.

  앵귤러 컴포넌트가 웹 컴포넌트로도 패키지 된다. 


ng-conf 2015에서 미스코가 말한 Angular1에서 Angular2로 가면서 Syntax 표현이 바뀌게 된 이유와 Web Components를 어떻게 Angular2와 결합해 사용할 수 있는지 설명한다. 좀 박수를 쳐주고 싶다. 놀라울 따름... 잘 모르겠으면 아래의 디자인 가이드를 보자. 




Custom Element Interface


브라우저가 제공하는 엘러먼트와 Custom 엘러먼트는 구분을 하지 않고 모든 것을 DOM element로 보는 것이 웹 컴포넌트임을 전제로 한다. DOM element의 API 구성은 다음과 같다.


  - DOM events

  - DOM attributes 

  - Element properties 

  - Element methods 


DOM은 Domcument Object Model의 약어이고 이는 HTML/XML을 객체로 변화하고 객체 안에 어떤 API가 있어야 하는지 정의해 놓았다. 일단 모든 DOM의 최상위 인터페이스는 Node이고 하위로 크게 3가지인 Document, Element, Text가 존재한다. Custom element API는 events, attributes, properties, methods를 갖는다고 볼 수 있고 해당 엘러먼트에 대해서 두가지 관점으로 접근한다면 "사용하는 입장(Consumer)"과 "엘러먼트는 구현하는(implementer) 입장"이 있다. 먼저 엘러먼트 접근 사용하는 경우를 보자. 



Interface Consumer


접근해서 사용하는 입장의 4가지 경우. 여기서 native와 custom element간의 접근/사용에 대한 방법상의 차이는 전혀 존재하지 않는다.

var element = ... // element를 얻어온다


// DOM attributes

element.setAttribute('title', 'hi dowon');


// DOM event

element.addEventListener('change', ...);


// Element property 

element.value = 'youngsik';


// Element method 

element.focus();



Interface Implementer


Element를 구현하는 입장은 Custom Element를 만드는 것이고 Native element와 동일한 위치를 부여 받도록 Web Components 스펙을 제정한 것이다. 이때 구현을 위해 필요하는 영역으로 크게 4가지가 있다. 


  - Custom Element

     엘러먼트를 어떻게 만들지 API를 정의한다. 

     HTMLElement.prototype을 확장한다


  - HTML Templates

    엘러먼트의 내용을 Out of Box로 분리해서 만드는 방법을 정의한다. 

    <template id="my-element"> 태그안에 정의


  - HTML imports

    템플릿의 확장자는 .html이고 이를 import하는 방법을 정의한다. 비동기적 import. 

    <link rel="import" href="my-element.html"> 


  - Shadow DOM

    템플릿을 import한 후 사용하는 방법을 정의한다. Out of Box로 JS, CSS를 격리(Boundary)한다. 

    Document 노드 밑에 Root Node는 HTML 이듯이 Custom Element 밑의 Root 는 Shadow Root (Node)이다. 

    var shadow = node.createShadowRoot(); 처럼 Component안에서 Root Node 역할을 하는 Shadow Root Node를 생성한다. 




DOM Elements and Data-binding Frameworks


Custom Element와 앵귤러와 같은 프레임워크간에 데이터 바인딩을 위해 별도의 규칙을 강제하면 안된다. 이러한 문제를 프레임워크에서 해결을 해야한다. 데이터 바인딩에는 두가지 방향이 있다. 


  - 엘러먼트에 데이터 쓰기

     native 엘러먼트에 있는 API를 사용해 property 또는 attribute에 접근한다.  

  

  - 엘러먼트에서 데이터 읽기 

    항시 property를 읽어온다. attribute는 tag 에 표현되는 정적이 값으로 최초에 설정되고 그 이후 바뀌지 않고 tag가 DOM tree안의 Object 변경되어 property를 접근할 수 있다. 또한 attribute는 변경이 안되지만 property는 변경이 가능하다. 


앵귤러에서 이러한 property 변경을 감지하기 위해 Change Detection 이 필요하다. 일단 변경하는 방법은 엘러먼트의 속성(property)를 통해 아래와 같이 가능하다. 

// element 얻어오기 

var input = ...;


// 변경이 감지되었을 때 element의 property를 통해 변경을 수행

input.value = newValue;


그렇다면 변경의 감지는 어떻게 할까? 프레임워크도 DOM events를 이용해서 아래와 같이 감지하는 방법이 필요하다. 

var input = ...;


// 프레임워크가 input event를 등록한다. 

imput.addEventListener('input', () { 

   // 값을 읽어온다. 

   var newValue = input.value;


  // 변경 감지 시스템에 알려준다. 

  framework.notify(input.value);

});


그럼 Web Components 스펙으로 작성된 엘러먼트의 내부 상태 정보의 변경을 어떻게 Detection할까? 이에 대해 Web Components에는 스펙이 없다. 따라서 native element의 기존 이벤트를 적절히 사용해야 한다. 



Angular and Web Components


앵귤러가 Custom 엘러먼트를 어떻게 사용하는지 프롬프트 창에 텍스트를 입력하는 Web Components 예제를 통해 알아보자.


 

프롬프트 API 


  - Element properties 종류 

    + title: 다이얼로그 타이틀 텍스트

    + content: 사용자가 타이틀을 수정하면 반영될 property. 컴포넌트는 edited 이벤트 발생 

    + visible: 다이얼로그가 보이면 true, visible의 변경에 따라 open/closed 이벤트 발생


  - Element methods 종류 

    + open()

    + close()


  - DOM Attributes

    + accept-text: accept 버튼에 보여지는 텍스트

    + cancel-text: cancel 버튼에 보여지는 텍스트 


  - DOM Events 

    + accepted: accept 버튼을 클릭하면 발생하는 이벤트 

    + canceled: cancel 버튼을 클릭하면 발생하는 이벤트 

    + open: 다이얼로그 박스가 오픈하면 발생하는 이벤트

    + close: 다이얼로그 박스가 닫히면 발생하는 이벤트 

    + edited: 다이얼로그박스에 텍스트 입력할 때 발생하는 이벤트 


var Prompt = Object.create(HTMLElement.proptype, {

  // property 정의

  title: { get: function() {...}, set: function(value) {...}},

  content: { get: function() {...}, set: function(value) {...}},

  visible: { get: function() {...}, set: function(value) {...}},

  

  // attribute notification 정의 

  attributedChangedCallback(name, old, new) {...},


  // methods 정의

  open: function() {...},

  close: function() {...} 

});


document.registerElement('x-prompt', { prototype: Prompt });



Web Components 인스턴스 만들기


커스텀 엘러먼트로 사용하기 위해 registerElement를 해서 Web Component로 등록을 하면 이름을 통해 언제나 DOM Element를 인스턴화할 수 있다. 아래 보는 것처럼 앵귤러에서 사용하기 위해 앵귤러에서 추가 조치로 해야할 것이 없다. 

<div ng-repeat="item in items">

  <x-prompt></x-prompt>

</div>

 

 

이벤트 리스닝(Listening)


Custom Element가 자신의 open 이벤트를 발생시키면 Angular에서 리슨(Listen)할 수 있어야 한다. Angular 1에서는 ng-click같은 디렉티브가 설정되어 있어야 리슨을 할 수 있었다. 즉, 버전 1 로 하면 open 이벤트에 대한 디렉티브가 있어야 한다는 소리이다. 그래서 Angular 2는 디렉티브 없이 on-*을 붙이면 커스텀 이벤트도 리슨할 수 있게 제안하고 있다. (위에 ng-conf 2015에서 미스코가 후반부에 Web Components 사용예를 보면 (close)="doSomething()" 으로 처리한다. 예제)

<x-prompt on-close="doSomething()">


 

Attributes/Properties에 expression 바인딩하기 


앵귤러 데이터 바인딩은 expression이 변경되면 연결된 destination 값도 자동으로 바뀌게 한다. 앵귤러는 attribute 또는 property에 변경된 값이 쓰여지는 것을(be written) 알아야 한다. 실험적으로(heuristic) 아래와 이를 찾아낸다.

function updateValue(element, name, value) {

  // register에서 행위를 찾는다

  var useProperty = checkOverrides(element, name);

  if(useProperty == null) {

    // use heuristic

    useProperty = name in element;

  }

  if(useProperty) {

    element[name] = value;

  } else {

    element.setAttribute(name, value);

  }

}


위의 방법으로 다음을 해석해 보면

<x-prompt accept-text="I {{name}} accept" 

                   cancel-text="Cancel"

                   bind-title="contract.name">


  - cancel-text="Cancel"; 은 cancel-text Attribute 값으로 "Cancel" 문자를 updateValue(element, 'cancel-text', 'Cancel');로 호출한다. 

  - accept-text="I {{name}} accept"; 도 accept-text Attribute 값으로 expression을 문자로 변화하여 updateValue(element, 'accept-text', 'I ' + name + ' accept'); 로 호출한다. 

  - bind-title="contract.name"; 은 양방향 바인딩으로 엘러먼트의 title 프로퍼티와 contract.name이 맵핑어 expression이 바뀌면 title 프로퍼티도 업데이트 되어진다. 



Web Components 얻


최신 소스를 보면(ng-conf 2015의 youtube 예제) 다음과 같이 나온다. (앵귤러 디자인 가이드 문서를 보면 역시 글로 쓰여진 것의 업데이트하기는 거기나 여기나 비슷한가 보다. 써놓고 신경을 쓰지 않으면 업데이트 하기가 힘들어서 초기의 생각의 흔적만을 볼 수 있고, 변경된 최신 내역을 볼 수가 없다. 여기서도 마찬가지다)

<google-youtube chromeless="0"

   #player

   [videoid]="video.id"

   (googleyoutubestatechange)="setState($event.detail.data)">

</google-youtube>

<button *ng-if="!isPlaying()" class="md-button md-default-theme md-raised"

      (click)="player.play()">Play

</button>

<button *ng-if="isPlaying()" class="md-button md-default-theme md-raised"

      (click)="player.pause()">Pause

</button>


  - 일단 google-youtube Web Components에 #player 로컬 변수를 선언하고 Button 태그에서 사용을 하고 있다. 

  - (click)="player.play()" 또는 (click)="player.pause()"를 호출하고 있다. 

  - 디자인 문서에 따르면 on-click="player.play()" 그리고 ng-id="player", bind-videoid="video.id"와 같이 표현을 해야할 것이다. 어떻게 하든 많은논의를 통해 디자인에서 이야기 내용이 좀 더 발전적으로 변경된 모습을 볼 수 있다. 



엘러먼트의 property/attribute 발견하기(Detecting)


커스텀 엘러먼트는 DOM attributes와 Element property를 변경할 수 있다. DOM attributes는 DOM4의 DOM Mutation Observers를 이용해 찾을 수 있지만 Javascript나 DOM API를 이용해 Element property의 변경을 발견하는 것은 쉽지않다. 그래서 앵귤러는 실험적(heuristic)으로 컴포넌트의 이벤트를 찾아 리스닝(Listening)을 하고 변경이 발생하면 컴포넌트에 대해서만 dirty checking을 하여 변경을 반영함으로 부정적인 성능 저하를 제거한다. (즉, 해당 엘러먼트의 이벤트를 찾아 리스닝을 걸어 놓는 방법은 Native에서 많이 사용하는 방법이고 이를 그대로 활용한다. Angular 1에서는 dirty checking이 모든 $scope의 변경을 체크했다면 이제는 컴포넌트 변경안에서만 dirty checking이 일어아는 샘이다)



다음 장에서는 앵귤러 컴포넌트가 어떻게 웹 컴포넌트가 될 수 있는지 알아본다.



<참조> 

  - Angular v2.* 의 Web Components 관계 정립 문서  

  - Web Components 소개 

  - Angular + Web Components 사용 youtube 예제 소스 (ng-conf 2015)




posted by 윤영식