잊지 않겠습니다.

angularJS를 이용한 single document web application을 개발 할 때, 내용에 대한 정리가 필요해서 한번 정리해봅니다.

angularJS MVVM 이해하기


(그림의 출처는 http://www.mimul.com 입니다.)

Spring MVC를 이용하고 있다면, MVC의 개념에 대해서는 잘 이해하고 있을겁니다. 개인적으로 javascript framework에서 이야기하는 MVVM이라는 것은 약간의 marketing 용어라는 느낌이 강하게 들기 때문에, 거의 기존의 MVC와 동일하게 생각하면 됩니다.

Model

데이터 관리 및 통신등의 역활을 담당합니다. 이는 Spring MVC를 이용한 개발시에 표현되는 @Service와 동일하게 생각해도 됩니다. Service와 Factory로 구성이 되며 이는 singleton 여부와 동일하게 생각하면 됩니다. Service는 singleton으로, factory는 prototype으로요.

주로 사용되는 것은 REST API를 호출하는 call layer로 구성이 되는 경우가 많습니다. 이에 대한 결과에 대한 caching과 logic을 처리하는 것이 주가 되는데 개인적으로 추천을 한다면 Restangular module을 사용하는 것이 보다 더 편하고 좋은 코드 가독성을 가지고 올 수 있습니다. (https://github.com/mgonto/restangular)

Controller

DOM event handling과 $scope를 이용한 DataBinding 역활을 담당합니다. Spring MVC에서의 Controller와 Model의 역활이 합쳐져 있다고 이해하시면 편합니다. @Controller : Controller class, Model: $scope 로 생각하시면 좋습니다. DataBinding과 DOM event handling을 모두 가지고 있기 때문에 $scope의 코드의 method 들은 최대한 간결하고 명확한 naming이 좋습니다.

View

HTML 영역입니다. View의 표현이 중복되는 것이 많아진다면 directive를 만들어서 처리를 하면 HTML의 재활용도를 높일 수 있습니다. 다만, 저같이 기억력이 안좋은 사람에게는 사용할 때마다 directive의 code를 다시 한번 확인해야지 되는 단점을 가질 수 도 있습니다.;

angularJS를 이용한 개발 시 유의점

Memory leak

이 상황은 single document application을 구성할 때, 가장 주의해야지 되는 내용입니다. 지금까지 javascript로 개발하는 상황과 많은 차이를 가지고 오는 부분이 이 memory leak 부분이라고 생각합니다. 이 부분에 대한 주의가 필요합니다. memory leak이 발생되는 상황임을 알 수 있는 방법은 chrome의 profile에서 memory snapshot을 얻어서 확인 할 수 있습니다. 일단 3가지 질문을 통해서 javascript의 memory leak이 발생되고 있는지를 확인 할 수 있습니다.

page가 너무 많은 memory를 사용하는 경우

너무나 많은 memory를 사용하고 있다면 chrome의 timeline memory view와 chrome task manager를 통해서 확인 할 수 있습니다. 경험적으로는 사용하지 않는 DOM을 계속해서 가지고 있는 경우. (unbind DOM, fragmented DOM)이 계속해서 생성이 되는 경우에 주로 이런 현상이 발생됩니다. 더 이상 필요없게 되는 DOM에 대한 event listener를 unbind 시키는 것이 매우! 중요합니다.

page가 memory leak에서 자유로운지

Chrome Profile에서 볼 수 있는 Object allocation tracker를 이용해서 javascript object의 할당을 확인해보는 것이 좋습니다. snapshot을 이용해서 GC가 일어날 때와 일어나지 않았을 때의 memory 차이를 알아보고 page가 이동이 된 후도 계속 남는 memory가 많은지를 확인해봐야지 됩니다.

Forcing GC가 자주 일어나는지

chrome timeline memory view를 이용하면 GC가 얼마나 자주 일어나는지를 확인할 수 있습니다. 이것이 자주 일어난다면 너무나 많은 객체들이 생성되고 있다는 것을 의미합니다. 객체를 너무나 많이 생성하고 있는 것에 대한 고민이 필요합니다.

위의 상황들중에서 가장 문제가 될 수 있는 것은 javascript의 fragmented DOM 또는 unbinded DOM에 대한 event listener로 인한 memory leak 입니다. 이는 jQuery UI를 angularJS single document application에서 사용하는 경우, 자주 발생할 수 있는데, 이는 각각의 component 들에 대한 event binding이 정상적으로 clear 되지 못해서 발생되는 문제입니다. 이 부분에 대해서는 다음 Blog post를 참조해보세요.

http://rinat.io/blog/angularjs-and-jquery-plugins-memory-leaks

이러한 unbinded DOM의 event listener를 해지하기 위해서는 $destroy event의 처리가 필요합니다.
다음과 같은 code snippet가 각 controller에서 위치해야지 됩니다. 이 부분은 사용되는 javascript UI framework 등에서 어떻게 memory에서 모든 객체들을 제거할 수 있는지에 대한 document를 참고하시는 것이 좋습니다. (jquery 기반의 UI component는 destroy() method를 갖는 것이 일반적입니다.)

$scope.on('$destroy', function() {
    var dateTimePicker = $scope.element('#dateTimePicker');
    if(dateTimePicker != null) {
        dateTimePicker = dateTimePicker.datetimepicker();
        dateTimePicker.destroy();
    }
});

또한, $destroy에서는 controller에서 사용되고 있는 모든 timer들의 해재역시 처리해주는 것이 좋습니다.

Directive의 올바른 이해와 사용

Directive를 올바르게 사용하는 것은 매우 중요한 문제입니다. 가장 우선적으로, Directive의 scope의 정의를 정확히 아는 것이 중요합니다. Controller의 $scope와 독립적인 scope가 생성 되나, 양방향 또는 단방향으로 controller와 통신을 하는 것이 가능합니다.

예를 들어, scope를 다음과 같이 선언할 수 있습니다.

    scope: {
      title: '@',
      parentCodeId: '@',
      data: '@',
      ngModel: '=',
      text: '=',
      name: '@',
      nullItem: '@',
      styleValue: '@'
    },

title, parentCodeId, data, name, nullItem, styleValue의 경우에는 attribute를 이용한 값 설정과 동일합니다. directive의 scope를 변경하는 것으로 controller가 변경되지 않습니다. 그렇지만, ngModel, text와 같이 ‘=’로 선언된 내용은 다릅니다. ‘=’로 선언된 내용은 directive를 통해서 수정된 값이 controller의 값을 변경시키게 됩니다.

이러한 양방향 DataBinding을 이용하게 되는 경우, Directive에서 event를 호출할 필요가 거의 없습니다. 최대한 event를 호출해서 처리하는 코드양을 줄이는 것이 속도면에서 유리합니다.

DataBinding의 처리

angularJS에서의 DataBinding은 $scope의 모든 변수에 대해서 이전의 값과 현재의 값을 비교하여, 값이 다른 경우 DOM을 갱신하는 방식입니다. $scope의 변수의 값을 비교하는 타이밍은 아래와 같습니다.

  • scope.$apply()가 호출될 때.
  • DOM event가 발생될 때. (onchange, onclick etc…)
  • http 응답이 발생된 경우. ($http, $resource)
  • url이 변경되는 경우
  • $timeout event가 발생되는 경우

이 부분에 대한 개선은 다음과 같이 처리하는 것이 좋습니다.

기본적인 angularJS module의 이용

jQuery와 angularJS를 혼용해서 사용하게 되는 경우가 많은데, 위와 같은 문제 때문에 최대한 angularJS의 모듈들을 사용하는 것이 좋습니다. 가장 좋은 것은 jQuery method를 사용하지 않는것이 좋습니다. 예를 들어 jQuery의 기본 $.ajax는 가장 자주 사용되는 http ajax 통신 method입니다. 그렇지만, 이 코드를 사용하는 경우, scope의 DOM update가 되지 않습니다. 따라서, $http service를 이용하는 것이 좋습니다.

또한, window의 경우에도 $window, document는 $document를 이용하는 것이 좋습니다. 그리고, WebSocket을 이용하는 경우도 역시 마찬가지의 문제가 발생됩니다. WebSocket의 promise를 반환하고 그 값을 얻어낼 때, $apply() method를 호출해주어야지 됩니다.

$watch 는 최대한 가볍게

$watch method는 잦은 호출이 발생될 수 있는 code입니다. 다음과 같은 처리가 반드시 필요합니다.

slow — Anything faster than 50ms is imperceptible to humans and thus can be considered as "instant".
limited — You can't really show more than about 2000 pieces of information to a human on a single page. Anything more than that is really bad UI, and humans can't process this anyway.

TIP

이 부분은 개인적으로 사용해보니 좋았던 TIP들입니다. 다른 분들은 다르게 느끼실 수 있는 부분입니다.

Parent Controller를 이용한 Controller common method의 공유

공통적으로 사용하고 싶은 method들을 MainController에 넣어두면 Controller를 작성하기가 매우 편해집니다. 예를 들어 back() 처리라던지, Confirm/Alert 과 같은 알람기능이나 resize에 대한 기본적인 처리와 같은것을 넣어두면 매우 유용하게 사용가능합니다. 이를 지원하기 위해서 기존 ng-view 상단에 ng-controller를 지정해서 처리해주면 됩니다.

    <div ng-controller="MainCtrl">
      <section ng-view="" onload="completedRender()"></section>
    </div>
angular.module('fmsPublicWebApp').controller('MainCtrl', function ($scope, $timeout, $window, Codeservice, $location) { 
    $scope.showGlobalMessage = function(type, title, message) {

    }
});
$http, $resource보다는 Restangular를 사용

bower를 이용해서 쉽게 추가 가능한 Restangular를 사용하면 좋은 가독성을 갖는 코드를 짜는 것이 가능합니다. 약간 옆으로 길어지는 코드 pattern이 생기긴 하지만, 직관적인 호출이라는 강점을 가질 수 있습니다.

jQuery Library를 사용할 때는 주의할것!

최대한 jquery에 의존하지 않고 사용하는 것이 더 편합니다. angularJS는 jqLite라는 jquery의 mini version을 가지고 있습니다. 기본적인 jQuery select와 같은 기능들은 대부분 가지고 있기 때문에 angular.element를 사용하는 것이 더 좋습니다.

그리고, jQuery UI component를 사용하게 되는 경우에는, directive로 새로 만들어서 사용하는 것을 강력 권장합니다. 위에서 이야기드린 것처럼, jQuery UI library의 destroy를 정확하게 호출하지 않는 경우에는 memory leak이라는 매우 미묘한 문제를 가지고 오게 됩니다. 개발이 한참 진행된 이후에 memory leak을 발견하게 되는 경우에는 찾기가 정말 힘들어질 수 있습니다.

$scope에는 외부에 노출되어야지 되는 변수 및 method만을 정의 한다.

$scope의 값은 angularjs의 dirty-search의 대상입니다. 불필요한 내부값 및 내부 method 들을 $scope에서 사용해서 굳이 성능저하를 만들 필요는 없습니다.

Summary

AngularJS는 정말로 재미있는 Framework입니다. 특히 single document application을 개발할 때, 그 장점을 더 살릴수 있습니다. 그렇지만, 다음 항목들에 대해서는 좀더 주의가 필요합니다.

  1. memory leak
  2. DataBinding의 이해
  3. 재사용 가능한 HTML과 Controller


Posted by Y2K
,