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

예제2

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

'프론트엔드 > AngularJS 1.8' 카테고리의 다른 글

2. Expressions (번역)  (0) 2021.12.25
1. Data Binding (번역)  (0) 2021.12.25

AngularJS Expressions 와 Javascript Expressions 차이점

AngularJS Expressions은 Javascript와 비슷하긴 하지만 차이점이 있습니다.

  • Context : Javascript 에서 Expressions 는 global window 객체에서 연산되지만, Angular JS는 scope 라는 object에서 연산됩니다.
  • Forgiving : Javascript 에서는 undefined 프로퍼티를 연산하려고 했을 때, ReferenceError , TypeError가 발생하지만, AngularJS는 undefined를 null 취급합니다.
  • Filters : Expression 내에 data display 전 filters를 사용할 수 있습니다.
  • No Control Flow Statements : AngularJS Expression 에서는 conditonal, loops, exception 을 사용하지 않습니다.
  • No Function Declarations : AngularJS Expression 내에서 함수를 선언할 수 없습니다. (ng-init 도 예외는 아닙니다.)
  • No RegExp Creation With Literal Notation : AngularJS Expression 내에서 정규식을 사용할 수 없습니다. 대신 ng-pattern 을 사용합니다.
  • No Object Creation With New Operator : AngularJS 에서 new Operator를 사용할 수 없습니다.
  • No Bitwise, Comma, And Void Operators : AngularJS 에서 Bitwise , ',' , void operator를 사용할 수 없습니다.

Example

---

Context

AngularJS에서는 Javascript eval()을 사용하지 않습니다. 대신에 AngularJS 자체 $parse를 사용합니다.
AngularJS에서 자체적으로 window , document, location 에 접근할 수 없습니다. 이는 global 영역에 무분별하게 접근하여서 위험성이 큽니다.
대신 AngularJS 컨트롤러 내부에 있는 자체 $window ,$location 을 사용합니다.
context 자체에 접근하기 위해서 this 키워드도 사용 가능합니다.

---

Forgiving

Javascript 코드 내에서 a.b.ceval() 하게 되면, exception이 발생합니다.
하지만 AngularJS Expression 내부에서는, undefined가 반환됩니다.

{{a.b.c}}

No Control Flow Statements

3항 연산자(a ? b : c)를 제외한 flow control 구문을 AngularJS에서 사용할 수 없습니다. 이유는 AngularJS 핵심 철학에 따라 Controller에서는
어플리케이션 로직을 가질 수 없도록 하기 위함입니다.

No Function Declarations or RegExp creation with literal notation

Angular JS Expressions 내부에서 정규식을 사용할 수 없습니다. 이는 AngularJS가 템플릿 내부 model 변수를 변환하는 로직에 영향을 끼치는것을 막기 위해서 입니다. 대신 Expression Filter 사용을 고려 할 수 있습니다.


$event

ngClick , ngFocust Directive는 scope 안의 $event 객체를 사용합니다. 이 객체는 jQuery Event Object 입니다.

One-time Binding

:: 키워드는 One-time Expressions 입니다. 한 번 바인딩 되고 나면 그 후의 digest 과정에서 제외됩니다.
Angular에서 수행하는 Digest Loop 속도를 최적화하고 리소스 관리 차원에서 이점이 있습니다.

Value stabilization algorithm

One-time binding에서 expression 값은 digest cycle에서 값을 유지하고 있습니다.

  1. :: 로 시작하는 expression을 만났을떄 digest loop는 undefined가 아닌 한, 값을 V로 저장합니다.
  2. expression의 결과를 마킹하고, digest loop를 빠져나가면서 expression을 dirty-checking watching하는 스케줄에서 제외시킵니다.
  3. 평상시처럼 digest looprk 가 실행됩니다.
  4. undefined일 경우, dirty-checking watch 스케줄을 그대로 놔두고, step 1으로 돌아갑니다.

One-time binding이 구체적으로 어느 이점이 있는가 ?

1번 정해지면 변경되지 않는 부분들

<div name="attr: {{::color}}">text: {{::name | uppercase}}</div>

Bidirectional Binding을 사용하더라도, 파라미터가 변경되지 않습니다.

someModule.directive('someDirective', function() {
  return {
    scope: {
      name: '=',
      color: '@'
    },
    template: '{{name}}: {{color}}'
  };
});
<div some-directive name="::myName" color="My color is {{::myColor}}"></div>

'프론트엔드 > AngularJS 1.8' 카테고리의 다른 글

0. $compile (1)  (0) 2021.12.26
1. Data Binding (번역)  (0) 2021.12.25

개요

AngularJS 어플리케이션에서의 데이터 바인딩 이란, Model과 View 사이에서의 자동 동기화를 말합니다. 

Model 은 어플리케이션에서 유일하게 데이터를 가공하는 곳입니다. 

View는 Model의 변경에 따라 그 내용을 사용자에게 보여주는 역할을 합니다. 

Model에서의 변경은 View의 변경을 일으키고, View에서의 변경은 Model에서의 변경을 일으킵니다. 

일반적인 템플릿 엔진에서의 Data Binding 

일반적인 템플릿 엔진에서 데이터는 한 방향으로 흐릅니다. 

템플릿과 모델을 merge하는 과정을 거쳐서 view를 만들게 되면, 이 때 Model이 변경되어도 View에 반영되지 않습니다. 
사용자가 view에서 하는 action들 또한 Model에 영향을 미치지 않습니다. 
이 이유로 개발자가 직접 Model과 View사이를 주기적으로 동기화 하는 과정을 관리해야하는 번거로움이 있습니다.

 

AngularJS에서의 Data Binding

AngularJS에서 템플릿은 다르게 동작합니다. 템플릿은 ( 컴파일되지 않은 Html, Angular Directive등이 포함되어 ) 동시에 브라우저에 의해 컴파일 됩니다. 컴파일 과정의 결과물이 View입니다. Model에서의 변경사항은 즉시 View에 반영되고, 그 반대 역시 반영됩니다.

Model은 여전히 유일하게 Data를 가져오고 다루는 곳입니다. 위에서 일반적인 템플릿 엔진에서 개발자가 신경써야 했던, 데이터 동기화가 간단해 졌습니다.

'프론트엔드 > AngularJS 1.8' 카테고리의 다른 글

0. $compile (1)  (0) 2021.12.26
2. Expressions (번역)  (0) 2021.12.25

+ Recent posts