Anuglar JS 공부 <4> - module.directive

Posted by lib oimb
2019. 2. 19. 00:42 AngularJS

 

 

1. Directive

 

공부 <1>에서 아마 짧게나마 하지만 요점은 확실하게 공부 했다. 이번에는 좀더 자세하게 한번 공부해보자

일단 한번 지난 공부를 리뷰해보자

 

 

지시자는 앵귤러의 웹 컴포넌트 역할을 한다. 뷰 영역에 속한다. 지시자에는 Angular에서 제공하는 지시자(ng-app,ng-model,ng-...)과 사용자 정의 지시자(file-model,a-b-c)가 있다.

그림은 지시자가 해석되는 순서를 보여준다. 

 

1. HTML을 DOM으로 해석하고

2. 각각의 지시자에 대해 compile()을 진행한다. 결과로 DOM l객체와  link()함수를 반환한다.

3. 반환된 link함수에 대해 $scope를 주입시키게 되면 $digest loop가 돌게 되고 이후 자동을 $watch에 등록 되어진다.

4. 앵귤러 컨텍스트에 등록된 DOM은 브라우저에 렌더링하게 된다.

 

[1] DOM

var html = '<div ng-bind="exp">html object'</div>;

[2} compile

var linkFn = $compile(html);

[3] link($scope) injection

var element = linkFn($scope);

[4] append DOM

angular.element.append(element);

 

=> angular.element($compile(html)(scope));

 

 

2. Directive options

 

내 생각에 directive는 앵귤러의 핵심이라고 생각한다. 일반적인 자바스크립트나 제이쿼리가 제공하지 않는 기능들을 제공하며 이를 잘 컴포넌트화 시킨다면 재사용성이 대폭 증가하기 때문이다.

 

그럼 일단 예를 한번 보자

<!DOCTYPE html>

<html ng-app="appModule">

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body>

   <body ng-controller='Ctrl'>

        <my-title>

            <my-content class='table'

                ng-repeat='item in items'

                item-title='item.title'

                item-add='as'

                >

                {{item.content}}

            </my-content>

        </my-title>

    </body>

<script>

angular.module('appModule', [])  

   .controller('Ctrl', function($scope) {

   console.log('2');

      $scope.items = [

          {title: 'What is Directive?',

           content: '특정한 행위의 기능을 가진 DOM엘리먼트.'},

          {title: 'Custom Directive',

           content: '디렉티브를 직접 생성해보십시오.'},

          {title: 'Bye~',

           content: '디렉티브 이야기를 마치겠습니다.'}

      ];

      $scope.as="qwe";

      

   })

   .directive('myTitle', function() {

   console.log('1');

       return {

        

          restrict: 'E',

          replace: true,

          transclude: true,

          template: '<div ng-transclude></div>',

          controller: function() {

             var items = [];

             console.log('3');

             

             this.addItem = function(item) {

                items.push(item);

             }

          },

          link:function(sc,el,attrs){

          console.log(5);

          }

      };

   })

   .directive('myContent', function(){

   console.log('4');

       return {

           restrict: 'E',

           replace: true,

           transclude: true,

           require: '^^?myTitle',

           scope: { title:'=itemTitle',

            tte:'=itemAdd'

           },

           template : '<div>' +

                      '<div class="title" ng-click="click()" ng-bind="title"></div>' +

                      '<div class="body" ng-show="showMe" ng-transclude></div>' +

                      '</div>',

           link: function(scope, element, attrs, controller) {

            console.log(scope.tte);

               scope.showMe = false;

               controller.addItem(scope);

               scope.click = function click(){

                  scope.showMe = !scope.showMe;

               }

           }

       };

   });

</script>

</body>

</html>

 

굵게 표시한 부분이 directive가 제공하는 option들이다.  나름 실무를 경험하면서 주로 사용했던 option들을 꼽아보자면 scope , restrict , require , link 정도가 되겠다. 

이외에 option들을 먼저 살펴 보면 

1. transclude , 이것은 template 요소 안에 끼어들기?를 할 것인가 말것인가를 의미한다. 하위요소로 두겠냐는 말이 더 정확할거 같다.

2. controller , directive안에 무슨 controller가 또 있지? 라는 반응을 할수 있는데 이는 주로 디렉티브간 소통?(require)을 할때 사용한다. 다만 나는 디렉토리간 controller을 선언하여 소통한적은 없었으므로 사용하지 않았다.

3. template , 이것은 자바스크립트나 제이쿼리를 이용하여 작성하던 렌더링 방식과 비슷한 느낌이다. 다만 조금? 틀린점이 있다면 렌더링을 할때 scope 옵션의 '범위'에 맞게 조절하여 '컴포넌트'화 시켜 사용한다는 점이 차이점이라 할수 있겠다. 잘만 이용한다면 복붙(copy and paste)만으로 특정 기능을 제공하는 컴포넌트를 만들어서 사용할 수 있다.  다만 이는 유지보수에 있어 모두가 알수 있는 컴포넌트가 아닐수 있기 때문에 회사 내규에 따라 조심스럽게 다뤄야 한다. ( 떄론 잘만든 컴포넌트 보다 유지보수성을 위한 코딩을 따라야 할 필요가 있음)

4. replace 이는 template과 같이 사용하며 template에 작성한 html 요소들을 해당 directive에 대한 요소 객체랑 교체를 할 것인가 말것인가를 정하는 것이다.

 

자 위 4가지는 주로,, 아니 거의 사용하지 않은 옵션이다. 하지만 앞서 설명한 4가지 option은 내 기준 정말로 많이 사용한 옵션들이다. 따라서 이 4가지에 집중적으로 설명 하겠다.

 

1. Scope

구글에 AngularJS directive scope를 쳐보면 수많은 글들이 쏟아진다. 나 역시 그 글들을 전부는 아니어도 몇몇개의 글들은 정독하고 사용해보았다.

scope에서 제공하는 여러가지 옵션들중 내가 가장 많이 사용한 부분에 대해서 설명하고자 한다.

 

1. scope : false 

scope를 false로 두게 된다면 부모 scope를 공유하게 된다. 여기서 핵심은 새로운 scope 객체를 생성하지 않는다는 것이다. 보통 제이쿼리를 이용하여 돔객체를 조절하던 작업을 AngularJS에서는 scope : false로 두고 작업을 진행하게 된다.

 

2. scope : { data : '=' } 

scope에 대해서 {} 와 같은 선언을 하게 된다면 '고립된' scope 객체를 사용하게 된다. 여기서 부모 scope에 대한 객체를 참조할 필요가 있는 것에 대해서 = , @ , &  이 세 가지를 사용하여 객체에 접근하게 된다. 이 방법을 사용하는 가장 큰 이유는 '컴포넌트'화 이다. 주로 사용하는 '기능' 및 'html요소 ?' 등을 컴포넌트화 시켜서 기능이 필요할때 또는 뷰에 필요로하는 html 요소를 렌더링하거나 만들어 주어야 할때 이 '컴포넌트'화 시킨 디렉티브를 이용하면 재사용성이 대폭 증가하게 된다.

<!DOCTYPE html>

<html>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body>



<div ng-app="drinkApp">

    <div ng-controller="AppCtrl">

        <div drink at-sign="strawberry" equal-test="ctrlFlavor"></div>

    </div>

</div>

<script>

var app = angular.module("drinkApp", []);



app.controller("AppCtrl", function($scope) {

    $scope.ctrlFlavor ={tt: "blackberry"};

})



app.directive("drink", function() {

    return {

        scope: {

            atSign: "@",

            equalTest:"="

        },

        template: '<div>{{atSign}}</div>',        

        link: function(scope, element, attrs) {

            // 동일 의미

            console.log(attrs.atSign);

            console.log(scope.atSign);

            

            console.log(scope.equalTest);

        }

    }

})

</script>

</body>

</html>

 

2. restrict

 

restrict의 사용방법은 간단해서 주의해야 할점들만 보고 가자. 

 

A , E , C , M 

restrict는 위 4가지의 경우로쓰일 수 있으며 디렉티브를 어떤 방식으로 선언하여 사용 할것인가를 정한다. 

각각 차례대로

A - attribute 속성으로 사용하며 가장 주된 방식이다.

E - element  요소로 사용하며 내 모스트 2픽 방식이다.

C, M - 한번도 사용한 적 없으며 C는 절대로 삼가야 하는 방식이다. C는 class 형태로 선언하겠다는 것인데 AngularJS를 사용할 때 class를 가지고 dom객체를 조작하는 일은 없도록 해야한다. 

 

3. require

 

require는 영어 그래도 '필요로하는' 디렉티브를 선언하는 것이다. 여기서 '필요로하는' 디렉티브가 의미하는 것은 필요로 하는 디렉티브가 가지고 있는 controller 요소를 사용하겠다는 것을 의미한다. 잘 이해가 가질 않는다. 이는 예로 한번 보는게 빠르다.

 

<!DOCTYPE html>

<html ng-app="appModule">

<head>

 <meta charset="utf-8" />

 <title>AngularJS </title>

 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

 <style type="text/css">

 .nested {

  border: 1px solid red;

}

.notnested {

  border: 1px dashed blue;

}

</style>

</head>

<body ng-controller="MainCtrl">

  <p>Hello {{name}}!</p>

  <menu>

    top-level

    <menu>nested

      <menu>nested deep</menu>

    </menu>

    <menu>nested2</menu>

  </menu>

</body>

<script>

  var app = angular.module('appModule', []);



  app.controller('MainCtrl', function($scope) {

    $scope.name = 'World';

  });



  app.directive('menu', function () {

    return {

      restrict: 'E',

      require: '?^menu',

      controller: function($scope){

        

      },

      link: function(scope, element, attrs, ctrl) {

        console.log('ctrl: ', ctrl);

        

        if (ctrl) {

          element.addClass('nested');

        } else {

          element.addClass('notnested');

        }

      }

    };

  });

</script>



</body>



</html>

 

이 코드를 이해 했다면 require에 대한 이해는 끝이다. 다만 여기서 조금만 더 이해하고 가자

Prefix Search path Error raised if controller not found? Example Link function receives as 4th argument
(no prefix) Current DOM element that directive is applied to Yes tabset Controller object, if found.
? Current DOM element that directive is applied to No ?tabset Controller object, if found, `null` otherwise
^ Current DOM element that directive is applied to and its parents Yes ^tabset Controller object, if found.
^^ Parent elements of the DOM element that the directive is applied to Yes ^^tabset Controller object, if found.
?^ Current DOM element that directive is applied to and its parents No ?^tabset
^?tabset
Controller object, if found, `null` otherwise
?^^ Parent elements of the DOM element that the directive is applied to No ?^^tabset
^^?tabset
Controller object, if found, `null` otherwise

이 표를 한번 이해해 보자. 이해가 됬다면 예제 코드에서       require: '?^menu',     ====>       require: '?^^menu', 로 바꿔보자

표를 이해했다면 변화를 눈치 챌것이다 ^^

 

4. link

 

자 마지막 link를 설명해보자 가장 많이 사용하며 디렉티브 구현에 있어 핵심이라 할 수 있다. 또 이미 위의 예제 코드들에서 굉장히 많이 사용하였다. 따라서 위의 예제를 보며 이해해보자. link function 의 필수 요소인  scope , element , attrs   이 세가지를 한번 이해해보자 

 

1. scope - directive 내의 scope 객체이며 이 scope의 범위는 위에서 보았던 { scope : <???> } 에서 지정해준 범위에 따라 변한다.

 

2. element - directive가 있는 요소 객체 그 자체이다. 또 추가로 꿀팁?을 알려주자면 element는 기본적으로 $ (  )  형태로 감싸져 있는데 이는 제이쿼리 형태로 감싸져있는 것을 말하며 element.on() 과 같이 제이쿼리 형태를 사용할수 있다는 것이다. 여기서 하나 더 알려주자면 자바스크립트 형태로도 사용 가능하다. elment[0] 으로 사용한다면 이는 자바스크립트 형태로 dom 객체를 가져오게 된다. 즉 elment[0].addeventlistener 형태로 사용 가능하다는 말이다. ( 말이 어렵나? )

 

3. attrs  - directive가 선언 되어있는 dom 객체의 속성 '값'을 가져올 수 있게하는 것으로 사용방법은 간단하다. 

<directive  hahaha="abc"> 라는 디렉티브가 있을 때 console.log(attrs.hahaha) 형태로 값을 찍어본다면 abc가 찍힐 것이다.

 

 

후... 끝

다음 공부시간에 봅시다

이 댓글을 비밀 댓글로