Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Angular.js 1.2.x에서 달라진 점

1.2.0 timely-delivery (2013-11-08)
몇주 전 Angular.js의 새 안정버전인 1.2.0 timely-delivery가 나왔다. 그동안 1.0.7을 사용하고 있었기에 새 버전에서 달라진 점을 좀 찾아봤다.(개발 버전을 모니터링하고 있지는 않았기에...) Angular.js도 짝수 버전은 안정버진이고 홀수는 개발버전으로 진행하고 있는데 그래서 그동안은 1.0.x 버전이 안정버전으로 유지되고 동시에 1.1.x 버전이 개발되다가 이번에 1.2.x로 새 안정버전이 올라가기 시작한 것이다.

좀 정리된 문서를 찾아보려고 했는데 Angular.js 쓰는 사람들은 1.1.x 개발버전을 적극적으로 쓰고 있었는지 1.0과 1.2의 차이점을 설명한 글은 거의 없고 대부분은 1.1.5와 1.2의 차이점을 설명하는 글이 대부분이었다. 그래서 이번에 1.2를 사용하기 위해서 뭐가 바뀐지 파악할 겸 Changelog를 보면서 정리를 해봤다.(코드로 일일이 테스트한건 아니고 문서들을 보고 정리했기 때문에 혹 잘못설명된 내용이 있을 수도 있다.) 일반적으로 개발버전이 어느 정도 개발되면 그대로 안정버전으로 넘어가는데 Angular는 특이하게 1.1.5에서 1.2로 넘어가면서 새로운 변경을 많이 시도했다.(그래서 문제가 좀 많이 발생한듯...)

주요 변경사항

참고로 1.2.0에서 프로퍼티 이름에 언더스코어를 앞이나 뒤에 붙히면 Angular 표현식({{someProperty}})에서 접근할 수 없는 private 프로퍼티 기능이 추가되었는데 언더스코어를 이미 많이 사용하고 있어서 상당히 많은 문제가 발생했다. 그래서 이 기능은 1.2.1에서 다시 제거되었다.

디렉티브

  • 조건에 따라 DOM을 추가/제거할 수 있도록 AngularUIui-if를 도입해서 ngIf 디렉티브가 추가되었다.
  • 애니메이션을 위한 ngAnimate가 추가되었다.

    • 1.1.x 버전에서는 ng-animate 디렉티브를 기반으로 동작했는데 1.2.x에서는 CSS 기반으로 새로 작성하고 ng-animate 디렉티브는 제거되었다. 그래서 HTML에 .ng-enter, .ng-enter-active, .ng-leave, .ng-leave-active같은 클래스로 CSS 애니메이션/트래지션 효과를 줄 수 있다.(Remastered Animation in AngularJS 1.2 참고)
  • 터치방식의 디바이스를 위한 ngTouch 디렉티브가 추가되었다.

    • 1.1.x에서는 ngMobile로 추가되었다가 터치 기기가 모두 모바일 기기가 아니므로 ngTouch로 변경되었다.(angular-touch.js)
    • ngTouchngClick이 기존의 ngClick을 오버라이드해서 터치이벤트에 반응하고 비-터치 기기에서는 click 이벤트에 대응된다. 클릭시 스타일을 줄 수 있도록 ng-click-active CSS 클래스도 추가되었다.
    • 스와이프 액션을 위한 ngSwipeRight, ngSwipeLeft 디렉티브가 추가되었다.
  • ngRoutengResource처럼 별도의 모듈로 분리되었다. $route, ngView, $routeParams를 사용한다면 angular-route.js파일을 따로 불러와서 의존성에 추가해 주어야 한다.

    <script src="angular.js"></script>
    <script src="angular-route.js"></script>
    <script>
      var myApp = angular.module('myApp', ['ngRoute', 'someOtherModule']);
    </script>
    
  • ngFocusngBlur디렉티브가 추가되었다.

  • ngKeydown, ngKeyup, ngKeypress 디렉티브 추가되었다.
  • ngEventDirectivesngCopy, ngCut, ngPaste가 추가되었다.
  • HTML5 <detail>, <summary>태그를 토글할 수 있는 ngOpen 디렉티브가 추가되었다.
  • HTML5의 srcset 속성을 지원하는 ngSrcset 디렉티브가 추가되었다.
  • ngRepeat에 홀수번째, 짝수번째 요소를 나타내는 $even, $odd 프로퍼티가 추가되었다.(기존에는 $index, $first, $middle, $last 프로퍼티만 있었다.)
  • ngHtmlBindUnsafe가 제거되고 새로 추가된 ngHtmlBind로 대체되었다. ngHtmlBind는 SCE(Strict Contextual Escaping, 아래 참조)를 지원한다.
  • ngSwitch에서 ng-switch가 적용되지 않은 요소의 순서를 유지한다. 즉, <ul ng-switch="select"><li>1</li><li ng-switch-when="option">2</li></ul>일 경우 전에는 2,1의 순서로 나왔지만 이젠 1,2의 순서로 나온다.
  • 이전에는 ngIncludengView가 수정된 요소의 내용만 변경했지만 이제는 포함된 모든 내용을 매번 새로 만든다. 이로써 모든 내용을 포함한 단 하나의 rootElement가 언제나 존재해서 css 애니메이션을 쉽게 정의할 수 있게 되었다.


서비스

  • Strict Contextual Escaping 서비스를 위한 $sce 서비스가 추가되었다. Strict Contextual Escaping은 특정 컨텍스트를 안전하다고 간주할 수 있는 값에 바인딩하는 모드이다.(ng-bind-html-unsafe로 사용자가 제어하는 HTML을 바인딩하는 컨텍스트) 1.2에서는 SCE가 기본적으로 활성화 된다.(IE8 quirks에서는 지원하지 않는다.) SCE로 안전한 코드를 작성할 수 있고 XSS, 클릭재킹(clickjacking)등의 보안취약점을 감시할 수 있다.
  • 프로미스 객체의 자동 바인딩 기능이 제거되어서 $parse와 템플릿이 프로미스 객체를 분해하지 않는다.

    $scope.foo = $http({method: 'GET', url: '/someUrl'});
    

    이전에는 위와같이 사용한경우 바로 템플릿에서 {{foo}}를 사용할 수 있었지만 1.2에서는 다음과 같이 사용해야 한다.(이 기능은 좋아보였는데 없어졌네.)

    $http({method: 'GET', url: '/someUrl'})
      .success(function(data) {
        $scope.foo = data;
      });
    

    이 기능이 필요하다면 $parseProvider.unwrapPromises(true)를 실행해서 활성화 해야 한다.

  • $resource가 더이상 $then 함수를 지원하지 않고 $promise 프로퍼티를 가지게 되었다. 그리고 $http 응답객체에 접근할 수 있도록 액션마다 interceptor가 추가되었다.

    • 이전에는 Resource.query().$then(callback);와 같이 사용했지만 1.2.x에서는 Resource.query().$promise.then(callback);와 같이 사용해야 한다.
    • Resource 메서드가 프로미스 객체가 아니라 인스턴스 자체를 반환하게 변경되었다.

      resource.$save().chaining = true;
      

      이전에는 위와 같이 사용했다면 1.2에서는 다음과 같이 사용해야 한다.

      resource.$save();
      resource.chaining = true;
      
    • 성공했을 때 Resource 프로미를 HTTP 응답 객체가 아니라 Resource 인스턴스로 처리해야 한다. 그래서 HTTP 응답객체에 접근하려면 interceptor를 사용해야 한다.

      Resource.query().$then(function(response) {...});
      

      이전에는 위와 같이 사용했다면 1.2에서는 다음과 같이 사용한다.

      var Resource = $resource('/url', {}, {
        get: {
          method: 'get',
          interceptor: {
            response: function(response) {
              // expose response
              return response;
            }
          }
        }
      });
      
  • $compile가 다중 엘리먼트 디렉티브를 지원해서 다음과 같이 사용할 수 있다.(ngInclude나 다른 디렉티브들도 순회할 수 있다.) 그래서 디렉티브의 이름은 -start-end로 끝날 수 없다.

    <tr ng-repeat-start="item in list">I get repeated</tr>
    <tr ng-repeat-end>I also get repeated</tr>
    
  • $route에서 슬래시를 포함한 경로를 모두 포함할 수 있는 named wildcard 기능의 문법이 변경되었다.

    $routeProvider.when('/Book1/:book/Chapter/:chapter/*highlight/edit',
            {controller: noop, templateUrl: 'Chapter.html'});
    

    이전에는 이름을 가진 와일드카드 파라미터를 위처럼 *highlight로 사용했지만 이젠 다음과 같이 :highlight*로 사용한다.

    $routeProvider.when('/Book1/:book/Chapter/:chapter/:highlight*/edit',
            {controller: noop, templateUrl: 'Chapter.html'});
    
  • $q에서 promise.alwayspromise.finally로 변경되어서 $qQ 프로미스 라이브러리를 사용할 수 있다.

    $http.get('/foo').always(doSomething);
    

    이전에는 위와 같이 사용했지만 1.2에서는 다음과 같이 사용한다.

    $http.get('/foo').finally(doSomething);
    

    IE8 같은 브라우저에서는 ES5를 지원하지 않으므로 다음과 같이 사용해야 한다.

    $http.get('/foo')['finally'](doSomething);
    
  • $controller가 "Controller as"라는 문법을 지원해서 ng-controller="MyController as my"와 같이 사용할 수 있게 되었다. 이렇게 작성할 경우 MyController객체의 my라는 인스턴스를 만들어서 사용한다.(프로토타입 속성을 가지는 컨트롤러를 정의하고 인스턴스를 별도로 만들어서 사용할 수 있는 용도로 보인다.)

  • setInterval을 감싸는 $interval 서비스가 추가되었다.
  • 모바일 브라우저에서 클릭해서 전화를 걸 수 있도록 $compile가 링크(a[href])에 tel:을 지원한다.
  • $location.search가 같은 값을 가지는 다중 키를 지원하게 되었다.(배열에 저장된다.) 이전 버전에서는 parseKeyValue의 마지막 키가 이전의 모든 키를 덮어쓰고 toKeyValue는 콤마로 구분된 문자열로 키를 합쳤다.(이렇게 사용하고 있었다면 1.2에서는 꼭 수정해야 한다.)


테스트

  • e2e 테스트에서는 애니메이션을 자동으로 비활성화한다.
  • angular mocks가 mocha를 지원하게 되었다.
  • ngScenario DSL에 dbclick, mouseover 메서드가 추가되었다.
  • 시나리오 테스트에서 mousedownmouseup이벤트가 추가되었다.
  • ngMock에서 편의를 위해서 module에 object 리터럴을 전달할 수 있다.
  • ngMock의 테스트코드내에서 동적 스타일시트를 생성할 수 있게 되었다.
  • ngScenario에서 browserTrigger가 마우스 이벤트의 파라미터 대신 eventData객체를 사용하도록 변경되었다. 1.2에서 사용하려면 keys, x, y 파라미터를 객체로 만들어서 browserTrigger함수의 세번째 파라미터로 전달해야 한다.

그 외...

  • angular.noConflict가 추가되어서 Angular의 네임스페이스가 충돌하는 경우 처리할 수 있다.
  • jQuery를 이전 버전에서는 전역적으로 참조하다가 1.2에서는 지역범위로 참조하게 변경되었다. 그래서 Angular.js가 아닌 코드에서 쉽게 jQuery를 사용할 수 있다.
  • <a><img>를 제외하고는 src 속성에 단 하나의 표현식만 사용할 수 있게 되었다.(이는 XSS와 관련된 보안을 강화한 것이다.) 즉, 1.2에서는 <iframe src="{{baseUrl}}?a={{a}&b={{b}}">와 같이 사용할 수 없으므로 자바스크립트에서 하나의 값으로 만든 후에 <iframe src="{{fullSrc}}">로 사용해야 한다.
  • DOM 이벤트 핸들러에서는 인터폴레이션을 사용할 수 없게 되었다. 이전 버전에서는 $scope.foo = 'alert(1)';와 같이 정의하고 <div onclick="{{foo}}">와 같이 사용할 수 있었지만 자바스크립트 코드를 평가하는게 안전하지 않으므로 1.2에서는 scope.foo = function() { alert(1); }로 정의하고 <div ng-click="foo()">와 같이 사용해야 한다.
  • 폼 이름에 표현식을 사용할 수 있게 되어서 <form name="ctrl.form">와 같이 작성하면 $scope.ctrl.form로 접근할 수 있게 되었다.(이전에는 $scope['ctrl.form']로 접근해야 했다.)
  • <input>의 이름으로 hasOwnProperty를 사용할 수 없게 되었다. 이전 버전에서는 경고도 없이 이러한 input이 스코프에 추가되지 않았는데 1.2 부터는 badname 예외를 던진다.
  • $compileProvider로 설정한 화이트리스트를 안전한 URL을 점검하는데 사용할 수 있게 되었다. 기본적으로 image/* mime 타입을 가진 data: URI를 포함한 일반적인 프로토콜 접두사가 화이트리스트에 포함되어 있다. 이 변경사항은 어플리케이션에 영향을 주지는 않는다.
  • select[multiple]에 바인딩할 수 없도록 변경되었다. 이 기능은 원래 양방향 바인딩이 동작하지 않으므로 아무도 사용하지 않았다.
  • 고립된 스코프를 갖지 않는 디렉티브는 같은 엘리먼트에서 고립된 디렉티브에서 고립된 스코프를 가져오지 않는다. 이 동작에 의존하고 있다면 로컬에서 스코프를 사용하기 위해 명시적으로 전달하도록 고립된 디렉티브를 변경해야 한다.

    <input ng-model="$parent.value" ng-isolate>
    
    .directive('ngIsolate', function() {
      return {
        scope: {},
        template: '{{value}}'
      };
    });
    

    이전에는 위와 같이 사용했다면 1.2에서는 다음과 같이 사용해야 한다.

    <input ng-model="value" ng-isolate>
    
    .directive('ngIsolate', function() {
      return {
        scope: {value: '=ngModel'},
        template: '{{value}}
      };
    });
    



그 밖에 수정된 기능

  • FormController가 폼을 원래의 상태로 리셋할 수 있게 되었다.
  • 스코프내에서 컬랙션을 감시하기 위한 $watchCollection가 추가되었다.
  • angular.bootstrap가 지연된(deferred) 부트스트랩핑을 지원한다.(테스트 러너나 Batarang에서 유용하다.)
  • ngModelngTrim속성을 지원해서 인풋의 trim여부를 지정할 수 있게 되었다.(trim을 하는게 기본값이다.)
  • $log$log.debug() 추가되었다.
  • batarang에 transcluded 스코프와 isolate 스코프 정보도 나온다.
  • $watch에 표현식이 상수면 한번만 평가한다.
  • $interpolate이 실행시 시작/종료 심볼(기본값은 {{}})을 노출해서 변경할 수 있게 되었다.
  • ng-options에서 "track by [trackByExpression]" 문법을 사용해서 객체의 동일함을 확인하는 표현식을 사용할 수 있다.
  • ngRepeat에서 각 아이템을 추적할 수 있는 트래킹 함수를 사용할 수 있다.(같은 키를 가지면 오류가 발생할 수 있다.).
  • ngInclude에 몇개의 인클루드가 보내지고 완료되었는지 확인할 수 있도록 $includeContentRequested 이벤트가 추가되었다.
  • $digest$apply밖에서 $evalAsync 큐에 작업목록을 추가했으면 다음 차례(next tick)에 새로운 $digest가 스케쥴링된다. 흔한 경우는 아니지만 프로미스의 비동기를 보장하기 위해서 $evalAsync 큐를 사용하는 $q 프로미트를 $digest외부에서 처리할 수 있다.
  • e2e 테스트에서는 애니메이션을 자동 비활성화한다.

  • $http

    • XHR의 커스텀 responseType을 지원한다.
    • 기본적으로 withCredentials 설정을 허용하게 되었다.
    • XSRF헤더와 쿠키명을 오버라이드할 수 있다.
    • timeout 인자가 프로미스 객체이면 프로미스객체가 처리되었을 때 요청을 거절한다.
    • PATCH 요청시 기본 content type 헤더가 추가되었다.
    • JSONP 요청에 타이아웃을 지원한다.
    • request/response의 프로미스 체이닝을 지원한다.
    • $http.defaults.cache에 기본 캐시가 추가되었다. 기본 캐시를 끄려면 요청 설정에서 {cache: false}를 설정해야 한다.
    • 헤더값에 함수를 사용할 수 있다.
  • $resource

    • 액션마다 커스텀 헤더를 지원한다.
    • 모든 $http.config 액션(transformRequest, transformResponse, cache 등)을 지원하게 되었다.
    • 리소스 파라미터를 함수로 정의하면 기본 리소스 파라미터가 런타임시에 계산되는 동적 파라미터로 사용한다.
    • 리소스 액션의 URL을 오버라이드 할 수 있다.
  • $compile

    • postlink 함수에서 DOM 구조를 변경할 수 있다.
    • 디렉티브가 인터폴레이션된 속성을 수정할 수 있다.
    • templatetemplateUrl 프로퍼티를 함수로 정의해서 동적으로 생성할 수 있다.
    • ngAttr*로 속성 바인딩을 지원한다. SVG등 브라우저가 인터폴레이션이 진행되기 전에 파싱을 해서 생기는 오류를 막기 위해서 ng-attr-, ng:attr:, ng_attr_등의 접두사를 사용하면 바인딩이 완료되었을 때 속성을 설정한다.
    • =?로 바인딩을 하면 정의되지 않은 값이라 하더라도 예외를 던지지 않는다.(=를 사용할 경우에는 NON_ASSIGNABLE_MODEL_EXPRESSION을 던진다.)
  • $parse

    • Angular 표현식에서 동등비교(===, !==) 지원한다.
    • constantliteral 프로퍼티가 추가되서 표현식이 상수값인지 리터럴(number, string, boolean 등) 알 수 있게 되었다.
    • 파서에서 삼항 연산자를 지원한다.
  • $q

    • $q.all()이 해쉬를 받는다.
    • 프로미스의 오류 핸들러를 쉽게 정의할 수 있도록 .catch()가 추가되었다.
    • 진행상태를 알 수 있는 deferred.notify() 메서드가 추가되었다.
    • 프로미스가 성공하든 실패하든 호출되는 $q.always() 메서드가 추가되었다.
  • $route

    • when의 template 파라미터에 함수를 사용할 수 있다.
    • URL 매칭을 할때 대소문자를 구별하도록 caseInsensitiveMatch 옵션이 추가되었다.
  • filter

    • linky 필터가 target(_blank, _parent같은)을 지원한다.
    • date 필터에서 밀리초를 지원하는 [.,]sss 포매터가 추가되었다.
    • 필터에 임의의 비교를 할 수 있는 Comparator 함수를 전달할 수 있다.
    • $ControllerProvider.register$CompileProvider.directive처럼 $FilterProvider.register에도 필터의 이름이나 팩토리의 맵을 전달할 수 있다.
  • jqLite

    • triggerHandler() 추가되었다.
    • ready()document.readyState=='complete'를 지원한다.
    • scope()가 격리된 스코프는 반환하지 않게 되었으므로 격리된 스코프의 디렉티브에서는 isolateScope()를 대신 사용해야 한다.
    • bind, unbind가 최신 jQuery처럼 on, off로 변경되었다.
  • 테스트

    • ngScenario DSL에 mouseover 메서드가 추가되었다.
    • ngScenario에서 <select>의 값이 없는 option일 경우 실패하도록 수정되었다.
    • $timeout-mockverifyNoPendingTasks 메서드 추가되었다.
    • ngMock이 특정시간의 지연을 주는 $timeout.flushNext가 추가되었다.
    • ngMock$timeout.flush에서 지연시간 제한을 파라미터로 받게 되었다.
    • ngMock이 데이터 파라미터를 매칭에 함수를 사용할 수 있게 되었다.


  • $sniffer가 자동으로 CSP 모드를 감지한다.(현재는 Chrome Canary에서만 지원한다고 나와있었는데 시간이 지나서 지금은 정확히 모르겠음.)
  • $interpolate가 컨텍스트를 가진 오류 메시지 지원한다.
  • $cacheFactory에서 cache.put시에 추가된 값을 반환한다.
  • ngSwitchngSwitchWhenngSwitchDefault가 다중 매칭을 지원한다.
  • $injector가 프로퍼티를 가지고 있는지 확인할 수 있는 has 메서드를 지원한다.
  • ngPluralize 에서 when 속성대신 속성으로도 매칭을 할수 있게 추가되었다.


참고자료

2013/11/23 05:17 2013/11/23 05:17