[컴][자바스크립트] 재사용 가능한 angular.js 위젯 만들기 - 치환하기



AngularJS 의 naming rule

기본적으로 AngularJS 의 이름 표기법에 익숙해지자. camelCase notation 과 hyphens notation 등을 혼용해서 사용할 수 있게 되어 있다. 예를 들면, ngHide 는 ng-hide 와 같은 이름이다.


directive 이름의 의미

ui 가 어떻게 움직여라라고 이야기하는 지시자 같은 느낌으로 이름을 지은 듯 하다.


어떤 식으로 사용할 것인가.

code
<na-flipbox></na-flipbox>


이 때 이름(여기서는 flipbox)은 되도록 자신만의 prefix를 사용하도록 권고하고 있다. 나중에 HTML 표준에 html element 이름이 추가되는 경우를 방지하기 위해서라고 한다. [ref. 2]



어떤 모습의 widget 인가.

code
<div class="flipbox">
 <div class="front">
  <div class="title">{{flipbox.title}}</div>
  <div class="description">{{flipbox.description}}</div>
 </div>
 <div class="back setting-box">
  <div class="date">...</div>
  <div class="item">...</div>
  <div class="advanced-option">...</div>
 </div>
</div>



위의 코드를 <na-flipbox> 대신에 넣어야 한다.(replace) 이러기 위해서 아래와 같이 code 를 작성하면 된다.
code
angular.module('naDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.flipbox = {
    title: 'rank',
    description: 'rank download'
  };
}])
.directive('naFlipbox', function() {
  return {
    restrict: 'E',
    template: 'naDirective.tpl.html'
  };
});



여기서는 색칠한 부분만 고려하자. 위와 같은 code 로 <na-flipbox> 는 naDirective.tpl.html 부분의 code 로 치환된다. 당연한 이야기지만, 위에서 우리가 치환해서 넣으려 했던 html code 는 naDirective.tpl.html 에 넣어놓으면 된다.

restrict

만약 여기서 restrict : 'E' 를 생략하면 기본적으로 attribute 에만 적용하게 된다. 그래서 '치환'이 되지 않는다.

attribute ? element ?
ref. 2 에서는 기존의 element 에 기능을 추가하는 것이라면 attribute 를 써서 directive 를 표시하고, 아예 하나의 component 라면 element 를 쓰라고 한다. 디자인 관점에서도 이 얘기가 맞는 듯 하다.


여러개의 $scope 


만약 우리가 위에 만든 directive 를 다른 title, description 을 사용해서 여러번 사용하려고 한다면, $scope.flipbox 의 값을 변경하기 위해 controller 를 여러번 만들어야 한다. 왜냐하면 controller 하나에 새로운 하나의 $scope 이 만들어지기 때문이다.[ref.4]

이런 과정은 정말 별로다. 그래서 directive 에서 사용되는 $scope 을 isolating 하는 방법을 알아보자.(고립화, isolation 이라는 말은 이해가 어려울 수 있다. 단순하게 생각하면 global 변수가 아닌, local 변수를 사용하는 개념이라고 보면 좋을 듯 싶다. 또는 scope 을 argument 로 받는 개념이라고 생각하면 될 듯 하다.)

angular.module('naDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.flip1 = {
    title: 'rank1',
    description: 'rank1 download'
  };
  $scope.flip2 = {
    title: 'rank2',
    description: 'rank2 download'
  };
}])
.directive('naFlipbox', function() {
  return {
    restrict: 'E',
    scope: {
      flipbox: '=info'
    }
    template: 'naDirective.tpl.html'
  };
});


위와 같이 directive 에 scope 을 정해주면, 저것은 "flipbox 를 info 라는 attribute 에서 가져와라" 라는 뜻이 된다. 이 directive 를 위해서 사용방법도 아래처럼 수정해 주면 된다.

<div ng-controller="Controller">
  <na-flipbox info="filp1"></na-flipbox>
  <hr>
  <na-flipbox info="flip2"></na-flipbox>
</div>


만약에 "flipbox 를 flipbox 라는 attribute 에서 가져와라" 라고 하고 싶다면 간단히 아래처럼 써주면 된다.

scope: { flipbox: '=' }
scope 에 할당할 수 있는 다른 값들(@, &, = ...)은 [ref. 6] 을 참고하자.


scope : {flipbox: '@'}
flipbox 를 element 에 있는 flipbox attribute 에 설정된 값을 가져와라. DOM 의 값을 가져오므로 항상 string 이 된다. 만약 위의 경우에 '@' 를 사용하면 flipbox 는 $scope.flip1 이 아닌 "flip1" 이라는 string 을 가지게 된다.

compilation
AngularJS 에서는 compilation 과정을 통해 event listener 들을 HTML 에 붙여준다. 이 과정을 통해 page 가 interactive 하게 된다.



template 이 insert 되는 곳

보통 element 를 치환하는 경우에 element 의 child element로 template 이 들어간다. 예를 들면 이런식이다.

<nae-frontbox>
<div class="front">
    <div class="title ng-binding"></div>
    <div class="description ng-binding"></div>
</div>
</nae-frontbox>



replace:true

만약,
<nae-frontbox>
부분을 없애고 이자리에 template 이 들어가길 원한다면, replace:true 를 사용하자. 그러면 아래처럼 html 이 만들어 질 것이다.

참고로, 여기서 주의할 점은 replace 를 사용하지 않으면 directive 의 child 로 생성되기 때문에 괜찮지만 replace 를 하는 경우에는 root element 를 하나둬서 묶어야 한다. 그렇지 않으면 console 에서 error 를 보게 된다.

<div class="front">
    <div class="title ng-binding"></div>
    <div class="description ng-binding"></div>
</div>

m.directive('naeFrontbox', function(){
  return{
    restrict: 'E',
    scope : false,
    replace: true,
    templateUrl : 'ui/naefrontbox.ui.tpl.html'
  };
});

자세한 사항은 ref. 3을 참고하자.


2개 이상의 diective

만약 내가 만든 directive 가 또 다른 directive 를 가지고 있게 하려면 어떻게 해야할까? 예를 들면 위의 flipbox 의 경우 frontbox 라는 directive 를 가지고 있게 하고 싶다.
이런 경우에는 module 하나에 flipbox directive 와 front box directive 를 정의하면 된다.

단, 이 경우에 scope 이 안만들어지고 부모의 scope 이 적용된다.



간단한 ui module example

ref.5 에서 input 값에 따라 input의 style이 다르게 적용되는 예제를 볼 수 있다.
ref.7 의 design pattern 이 개인적으로 마음에 든다.

간단히 여기에 ref. 7을 적용한 input 을 하나 만들었다. 이 예제는 click 시 text 가 input 창으로 변경되는 ui 이다.


naefocusinput.ui.tpl.html

<div class="focus-input">
    <input type="text" name="{{ inputId }}"
           ng-model="ctrl.title"
           ng-model-options="{getterSetter : true}"
           ng-show="ctrl.showInput"
           ng-blur="ctrl.hideEditInput($event)"
           focus-input-on="{{ctrl.showInput}}">

    <span class="title" ng-click="ctrl.showEditInput($event)" ng-hide="ctrl.showInput">
        {{ctrl.title}}
    </span>
</div>


naeFocusInput.coffee

###
    naeFocusInput
###
m = angular.module('naeFocusInputModule', [])
m.directive 'naeFocusInput', ()->
    {
        restrict: 'E'
        replace: true
        scope: {
            inputId: '@'
        }
        templateUrl: 'ui/naefocusinput.ui.tpl.html'
        controller: 'focusInputCtrl'
        controllerAs: 'ctrl'
    }
m.controller 'focusInputCtrl', ['$scope', ($scope)->

    this.showInput = false

    this.title= 'None'

    this.showEditInput = ($event) ->
        this.showInput = true
        return
    this.hideEditInput = ($event) ->
        this.showInput = false
        return

    return  # does not return anything, it will makes the controller not work

]
m.directive 'focusInputOn', ["$timeout", ($timeout)->
    {
        restrict: 'A'
        link: (scope, element, attrs)->
            attrs.$observe 'focusInputOn', (newValue)->
                if(newValue)
                    $timeout ()->
                        el = element[0]
                        el.focus()
                        el.selectionStart = el.selectionEnd = el.value.length
                        return

    }
]


ngIsolateScope

이 녀석이 class 에 들어있다면, local scope 가 만들어진 것이라고 보면 된다. local scope 를 위해서는 directive 에 scope 을 인자로 넣어야 한다. scope 을 전혀 정의하지 않으면 isolate scope 을 사용하지 않는 것으로 간주한다.

m.directive 'naeFocusInput', ()->
    {
        restrict: 'E'
        replace: true
        scope: {}
        templateUrl: 'ui/naefocusinput.ui.tpl.html'
        controller: 'focusInputCtrl'
        controllerAs: 'ctrl'
    }


ngModel
input 과의 통신을 위해서 ngModel directive를 사용하면 된다.



See Also

  1. javascript - Setting focus when showing a form input in AngularJS - Stack Overflow
  2. AngularJS Directive Design Made Easy
  3. Learn AngularJS With These 5 Practical Examples

References

  1. Angular.js – create reusable HTML widgets with directives, By Craig Atkinson on Aug. 13 2013
  2. Creating Custom Directives
  3. replace element in angularjs directive linking function, StackOverflow
  4. AngularJS: Developer Guide: Controllers
  5. AngularJS: API: ngModel
  6. AngularJS: Directives
  7. How I've Improved My Angular Apps by Banning ng-controller


댓글 없음:

댓글 쓰기