콤비네이션피자라지 2021. 12. 26. 17:48

Overview

HTML, DOM을 템플릿, 템플릿 함수를 생성합니다. 이로써 scope와 template을 연결 시킬 수 있습니다.
컴파일은 DOM tree를 탐색하고 Dom Element를 directive에 연결합니다.

Comprehensive Directive API

directive를 정의하는데는 여러가지 방법이 있습니다.
directive의 정의 객체를 반환하는 방법도 있고, postLink 함수만을 반환하는 방법도 있습니다.

Best Practice : 정의 객체를 리턴하는 것을 추천합니다.

var myModule = angular.module(...);

myModule.directive('directiveName', function factory(injectables) {
  var directiveDefinitionObject = {
    priority: 0,
    template: '<div></div>', // or // function(tElement, tAttrs) { ... },
    // or
    // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
    transclude: false,
    restrict: 'A',
    templateNamespace: 'html',
    scope: false,
    controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
    controllerAs: 'stringIdentifier',
    bindToController: false,
    require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
    multiElement: false,
    compile: function compile(tElement, tAttrs, transclude) {
      return {
         pre: function preLink(scope, iElement, iAttrs, controller) { ... },
         post: function postLink(scope, iElement, iAttrs, controller) { ... }
      }
      // or
      // return function postLink( ... ) { ... }
    },
    // or
    // link: {
    //  pre: function preLink(scope, iElement, iAttrs, controller) { ... },
    //  post: function postLink(scope, iElement, iAttrs, controller) { ... }
    // }
    // or
    // link: function postLink( ... ) { ... }
  };
  return directiveDefinitionObject;
});

directive를 정의할 때, 옵션을 명시해주지 않으면 기본 값을 사용하게 됩니다.
따라서 위의 directive 코드는 사실 아래와 같습니다.

var myModule = angular.module(...);

myModule.directive('directiveName', function factory(injectables) {
  var directiveDefinitionObject = {
    link: function postLink(scope, iElement, iAttrs) { ... }
  };
  return directiveDefinitionObject;
  // or
  // return function postLink(scope, iElement, iAttrs) { ... }
});

Life Cycle Hook

directive 컨트롤러는 angularJs 자체에서 정해진 directive 생명 주기에 따라 메서드를 제공합니다.

  • $onInit()
    • 모든 컨트롤러, Element의 Data Binding이 완료 된 후, pre, post link 함수가 실행되기 전에 정의된 컨트롤러 마다 실행됩니다.
  • $onChanges(changesObj)
    • (<)단방향 바인딩, (@)interpolation 바인딩이 갱신될 때마다 호출됩니다.
    • 파라미터인 changesObj 는 연결된 프로퍼티의 해시 값을 저장하고 있습니다. { currenctValue, previouseValue, isFirstChange()}
    • $scope 외부 프로퍼티의 값을 변경하는 일을 감지하는데 사용 할 수 있습니다. 바인딩이 초기화 된 후부터 호출된다는 것을 주의해야 합니다.
  • $doCheck()
    • digest loop 마다 실행됩니다.
    • 값의 변경을 감지할 수 있도록 합니다.
    • deep equality, 날짜 객체 검사, $onChange로 감지 불가능한 값의 변경을 감지 할 수 있도록 합니다. 비교를 위해서 이 함수를 이용할 때, 이전 값을 저장하고 있어야 합니다.
  • $onDestroy
    • scope가 제거될 때 호출됩니다.
    • 가지고 있는 외부 리소스를 해제할 때 사용할 수 있습니다.
    • $onDestroy 이벤트는 $scope.broadcast 가 수행되는 순서(top->down)대로 호출된다는 것을 기억하십시오. (부모가 먼저 호출된다.)
  • $postLink
    • directive 컨트롤러 자신과, 자식들이 link되고 난 후에 호출됩니다.
    • DOM event, DOM 조작을 수행할 수 있습니다.
    • 자식 element의 templateUrl 은 각각 비동기로 컴파일, 링크 과정을 거치기 때문에 접근할 수 없는 것을 명심하십시오.

Comparison with life-cycle hooks in the new Angular

새로운 Angular 역시 생명주기 훅을 사용하긴 하지만 다음과 같은 다른점이 있습니다.

  • AngularJS 훅에는 $ 기호가 붙지만, Angular에서는 ngOnInit 처럼 ng가 붙습니다.
  • AngularJS 훅은 컨트롤러 프로토타입으로 정의될 수 있고, 생성자 내부에서 정의될 수 있지만, Angular에서는 Component 클래스의 생성자 내부에서만 정의할 수 있습니다.
  • Angular에서 값의 변경을 감지하는 동작에 변경사항이 생겨서, Angular를 사용하게 되면 $doCheck 가 훨씬 더 적게 수행될 것입니다.
  • AngularJs에서는 $doCheck 가 어플리케이션 전체에 broadCast 되지만, Angular에서는 값의 변경이 Component를 벗어나는 것을 막았습니다. 비활성화는 enableProdModel() 로 가능합니다.

Life-cycle hook examples

예제1

See the Pen Untitled by jjh4698 (@jjh4698) on CodePen.

예제2

See the Pen Untitled by jjh4698 (@jjh4698) on CodePen.

Directive Definition Object

multiElement

  • 기본 값은 false 이다.
  • true 일 경우, Html 컴파일러에서 DOM 노드 중에서 directive-name-start directive-name-end 를 찾고, 함께 그룹화 한다.
  • ngClick 과 같이 반드시 엘리먼트에 event가 필요한 경우나 , ngInclude 와 같이 자식 element를 관리하는 directive 에서는 사용하지 않는것이 좋다.

priority

  • 1개의 Dom Element에 여러개의 directive가 매칭되어 있을 때 사용한다.
  • 이 값을 이용해서 directive가 컴파일 되는 순서를 정한다.
  • 숫자가 높은 순서가 높은 우선순위를 가진다.
  • pre-link function은 높은 우선순위부터 실행되지만, post-link는 반대로 실행된다.
  • 기본값은 0

terminal

  • true 일경우 directive 컴파일 우선순위의 가장 마지막에 넣는다.

scope

  • false, true 값중 1개로 설정할 수 있다.
    • false (default) : directive에 scope가 생성되지 않고, parent scope를 사용한다.
    • true : 새로운 자식 scope가, 부모 scope를 상속한채로 생성된다.
    • {...} (an object hash) : directive template에 새로운 isolated scope 가 생성된다. 부모 scope를 상속받지 않는다. template, templateUrl 이 없이는 자식 element에 isolated scope가 생성되지 않음을 주의하자. isolated scope object에서 상위 parent scope에 매칭되는 값을 정의할 수 있다.
    • @ or @attr : local scope 프로퍼티를 Dom attribute에 매핑시킨다. 결과는 Dom attribute 타입이 string이기 때문에 항상 string이다. 만약, attribute name이 따로 정의되어 있지 않으면 local name과 같다고 가정한다. 예를 들어서 <my-compnent my-attr="hello {{name}}"> 이 있다고 가정하면, isoated scope에서는 scope : { localName: '@myAttr'} 로 매핑되고, 실제 hello {{name}} 을 parent scope에서 읽게 된다.
    • = or =attr : 양방향 데이터 바인딩을 설정할 때 사용한다. parent scope에서 값이 계산된다. <my-component my-attr="parentModel"> 로 정의했을 때, isolated scope scope : { localModel: '=myAttr'} 로 매핑되고, parent scope의 parentModel 값에 따르게 된다. localModel, parentModel 값이 각각 변경되면 서로에게 영향을 미치게 된다. binding expression이 non-assignable일 때, $compile:nonassign이 발생한다. 기본적으로 $watch 메소드로 값의 변화를 추적하게 되며, 값이 array 일경우에 한해서 angular.equals 를 사용한다. 얕은 비교를 사용하게 할수도 있는데, 이때는 =* or =*attr 을 사용하면 된다.
    • < or <attr: 단방향 바인딩에 사용된다. parent context에서 계산된다. 같은 예제로 <my-component my-attr="parentModel"> 은 isolated scope scope : { localModel: '<myAttr'} 에 매핑된다. parent scope에서 parent Model이 변경됐을 떄, local Model 값이 변경된다. 역시나 얕은 비교를 위해서 <* , <*attr 로 정의할 수 있다.
        1. 같은 Model을 가르키고 있다. 값을 clone해서 갖고 있는것이 아니기 때문에, 주의 해야 한다.
        1. parent Model을 $watch 하고 있어서, isolated scope에서 바인딩된 객체를 바꾸었을 경우, event가 발생하지 않을 수 있으니 주의해야한다.
    • & or &attr: parent context의 expression을 실행하는 방법을 제공한다. <my-component my-attr="count = count + value"> 일 경우 isolated scope scope: { localFn : '&myAttr'} 로 매핑된다. localFn은 "count = count + value" expression을 래핑하고 있다.

4가지 종류 모두 ? 을 붙일 수 있다. optional , non-optional 을 의미한다.

app.directive('testDir', function() {
  return {
    scope: {
      notoptional: '=',
      optional: '=?',
    },
    bindToController: true,
    controller: function() {
      this.$onInit = function() {
        console.log(this.hasOwnProperty('notoptional')) // true
        console.log(this.hasOwnProperty('optional')) // false
      }
    }
  }
})

일반적인 경우 1개의 Element에 1개의 directive를 매핑시키지만, 아닐경우 매핑 방법이 여러가지 있을 수 있다.

  • no scope + no scope: 모두 부모 scope를 사용한다.
  • child scope + no scope: 1개의 child scope를 공유한다.
  • child scope + child scope: 1개의 child scope를 공유한다.
  • isolated scope + no scope: isolated scope만 자신의 scope를 사용하고, 나머지는 parent를 사용한다.
  • isolated scope + child scope: 동작하지 않는다.
  • isolated scope + isolated scope: 동작하지 않는다.

bindToController

bind scope 프로퍼티를 바로 컨트롤러에 매핑할 때 사용한다.

controller

컨트롤러 생성자 함수이다. pre-link 함수가 실행되기 전에 수행되며, 다른 directive에서 접근할 수 있다.
주입 가능한 local 변수들을 가지고 있다.

  • $scope: 현재 scope
  • $element: 현재 element
  • $attrs: 현재 attrs
  • $transcluede: 자식 element compile과정에서 사용할 변수. transclude link function function([scope], cloneLinkingFn, futureParentElements, slotName)

require

다른 디렉티브의 컨트롤러에 접근할 때 사용한다. link 함수의 4번째 파라미터를 컨트롤러로 만들 수 있다. 가능한 데이터 타입 string, object, array

  • (no prefix) : required controller를 현재 element에 위치시킨다. 없으면 에러
  • ? : required controller를 현재 element에 위치시킨다. 없으면 null
  • ^ : required controller를 현재 element, parent에서 찾는다. 없으면 에러
  • ^^ : required controller를 parent에서 찾는다. 없으면 에러
  • ?^: required controller를 현재 element, parent에서 찾는다. 없으면 null
  • ?^^: required controller를 parent에서 찾는다. 없으면 null