Angular :: Directive

Angular :: Directive

디렉티브

디렉티브의 구체적인 정의가 뭘까? 디렉티브는 템플릿 문법의 일환이 맞다고 봐도 무관할 것 같다. 디렉티브는 엘리먼트에 속성처럼 부여하여 DOM을 제어하기 위한 도구이다. HTML 요소의 특별한 동작이나 모양을 부여할 수 있다.


기본 어트리뷰트 디렉티브

  • [ngClass]에 Record 타입으로 된 값을 넘겨주면 해당 값에 맞게 클래스가 지정됨
  • [ngStyle]은 마찬가지로 오브젝트 타입으로 값을 넘기면 스타일이 지정됨
  • [(ngModel)]은 Form의 값을 바인딩하기 위해 주로 사용되는데 [] 프로퍼티 바인딩과 () 이벤트 바인딩이 동시에 필요하면 [()]과 같이 간략하게 표기할 수 있다.
    • 원레는 이렇게 [ngModel] (ngModelChange) 각각 바인딩하는데 프로퍼티 바인딩에 Change라는 이름이 붙은 emit 가능한 이벤트가 있다면 위와같이 간략한 표현을 사용하여 바인딩 할 수 있다.

🤔 [style] 이것도 오브젝트 타입이 지원되는데 [ngStyle]과 어떤 차이가 있는걸까?

[style]은 정적인 스타일링에 사용되는 요소이며 상태에 의존하여 스타일링이 변경되어야 하는 경우에는 [ngStyle]을 사용하면 된다. ng 붙은 속성들은 상태와 속성을 동기하기 위한 요소라고 봐도 될 듯?


기본 구조 디렉티브

  • *ngIf 에 바인딩되는 값이 false거나 null이면 해당 엘리먼트를 DOM 트리에 추가하지 않는다.
  • *ngFor="let item of items"와 같이 객체를 순회하여 엘리먼트를 렌더링 할 수 있다. 인덱스를 활용하려면 *ngFor="let item of items; let i=index"와 같이 선언하면 된다.
  • [ngSwitch]로 프로퍼티를 바인딩하고 *ngSwitchCase & *ngSwitchDefault로 선언하여 스위치문을 템플릿에서 사용할 수 있다.

구조 디렉티브는 한 엘리먼트에 동시에 사용할 수 없다. 왜냐면 구조 디렉티브는 간소하게 추상화된 동작이므로 동시에 사용하면 Angular가 어떻게 처리 해야할지 이해할 수 없다.

예를들어, 엘리먼트에 *ngIf를 선언하는 경우, Angular는<ng-template> 엘리먼트를 생성하고 *ngIf를 프로퍼티 바인딩 형태의 [ngIf]로 변환한다. 그리고 구조 디렉티브가 적용된 <div>에 클래스 어트리뷰트를 유지한 채로 <ng-template> 안쪽으로 옮긴다.

<div *ngIf="true">Rendered!</div>

위 구문은 다음과 같이 변경되어 처리된다.

<ng-template [ngIf]="true">
    <div>Rendered!<div/>
</ng-template>

엘리먼트 추가 없이 두 가지 구조 디렉티브를 사용해야 한다면 <ng-container>를 사용하면 된다.


ngNonBindable

ngNonBindable은 표현식이 평가되는 걸 방지하도록 해준다. 아래와 같은 평가식 같은 표현이 일반 텍스트로 표기된다.

<div ngNonBindable>{{ 1 }}</div>


커스텀 어트리뷰트 디렉티브

CLI로 간단하게 생성이 가능하다.

ng generate directive highlight

기본적으로 아래와 같은 형태로 만들고 ElementRef를 하면 현재 디렉티브를 사용한 엘리먼트를 가져올 수 있다. (🤔 document.quertSelector 된거랑 동일한걸까?)

import { Directive, ElementRef } from '@angular/core';

@Directive({
    selector: '[appHighlight]'
})
export class HighlightDirective {
    constructor(private el: ElementRef) {
        this.el.nativeElement.style.backgroundColor = 'yellow';
    }
}

해당 엘리먼트에 이벤트를 반인딩하려면 HostListener라는 데코레이터를 사용한다.

@HostListener('mouseenter') onMouseEnter() {
  // do something
}

@HostListener('mouseleave') onMouseLeave() {
  // do something
}

🤔 엘리먼트에 addEventListener 사용하면 어떻게 될까?

사용은 가능하지만 모든 SPA 프레임워크가 그렇듯 당연하게도 권장하지 않는 방법이니 사용하지 말자. (더 이상 이런 생각을 하지마..!)

@Input 데코레이터를 사용해서 값을 전달하도록 만들수도 있다.

@Input() appHighlight = '';

디렉티브 적용은 다음과 같이 selector에 명시한 값을 엘리먼트에 붙혀주면 된다.

<div appHighlight>Some Text</div>
// Input을 적용한 이후에는
<div [appHighlight]="">Some Text</div>


커스텀 구조 디렉티브

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({ selector: '[appUnless]' })
export class UnlessDirective {
    // 무분별한 리랜더링을 막으려고 사용한 듯?
    private hasView = false;

    constructor(
        private templateRef: TemplateRef<unknown>,
        private viewContainer: ViewContainerRef) { }

    @Input() set appUnless(condition: boolean) {
        if (!condition && !this.hasView) {
            this.viewContainer.createEmbeddedView(this.templateRef);
            this.hasView = true;
        } else if (condition && this.hasView) {
            this.viewContainer.clear();
            this.hasView = false;
        }
    }
}

이 디렉티브는 아래와 같이 사용하는데.

<p *appUnless="false">
  (A) This paragraph is displayed because the condition is false.
</p>

위에서 구조 디렉티브를 설명한 것과 동일하게 아래와 같이 바뀔 것이기 때문에

<ng-template [appUnless]="false">
    <p>(A) This paragraph is displayed because the condition is false.</p>
</ng-template>

template 내부에서 참조하는 VDOM을 this.templateRef로 참조하여 createEmbeddedView로 랜더링을 실행하는 동작으로 보여진다.

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.