어트리뷰트 디렉티브는 DOM 엘리먼트와 Angular 컴포넌트의 모습이나 동작을 변경합니다.
한 곳에서만 사용하는 동작은 템플릿 바인딩 활용하기
Angular 템플릿 문법만으로도 개별 엘리먼트의 클래스, 스타일, 프로퍼티, 이벤트를 변경할 수 있습니다.
- 클래스 및 스타일 바인딩은 CSS 클래스와 인라인 스타일을 추가하거나 제거합니다.
- 프로퍼티 및 어트리뷰트 바인딩은 DOM 프로퍼티와 HTML 어트리뷰트를 설정합니다.
- 이벤트 리스너는 사용자 상호작용에 반응합니다.
이러한 동작을 재사용 가능한 단위로 묶어서 어떤 엘리먼트나 컴포넌트에도 적용하고 싶다면 어트리뷰트 디렉티브가 유용합니다.
어트리뷰트 디렉티브 만들기
커스텀 어트리뷰트 디렉티브는 @Directive() 데코레이터가 있는 JavaScript 클래스입니다.
데코레이터의 selector는 디렉티브를 적용할 어트리뷰트를 정의합니다.
대괄호를 사용하면 어트리뷰트 셀렉터가 되므로 해당 어트리뷰트가 있는 엘리먼트에 디렉티브가 적용됩니다.
이름 충돌을 방지하려면 관례에 따라 app 같은 접두사를 사용하세요.
import {Directive} from '@angular/core';
@Directive({
selector: '[appHighlight]',
})
export class HighlightDirective {}
참고: CLI 명령어 ng generate directive를 사용하면 디렉티브와 테스트 파일의 기본 구조를 생성할 수 있습니다.
디렉티브는 호스트 바인딩으로 호스트를 선언적으로 변경하거나, 호스트 엘리먼트의 참조를 통해 명령형으로 변경할 수 있습니다.
다음 예제는 ElementRef를 주입받고 nativeElement 프로퍼티로 엘리먼트에 접근해서 배경을 노란색으로 설정합니다.
highlight.directive.ts
import {Directive, ElementRef, inject} from '@angular/core';
@Directive({
selector: '[appHighlight]',
})
export class HighlightDirective {
private el = inject(ElementRef);
constructor() {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
}
중요: 디렉티브는 네임스페이스를 지원하지 않습니다.
<p app:Highlight>This is invalid</p>
어트리뷰트 디렉티브 적용하기
엘리먼트에 디렉티브 셀렉터를 어트리뷰트로 추가하면 디렉티브를 적용할 수 있습니다.
app.component.html
<p appHighlight>Highlight me!</p>
Angular는 해당 <p> 엘리먼트에 HighlightDirective 인스턴스를 생성하고, 엘리먼트의 참조를 주입한 뒤 배경을 노란색으로 설정합니다.
사용자 이벤트 처리하기
사용자 상호작용에 반응하려면 @Directive() 데코레이터의 host 프로퍼티에서 호스트 엘리먼트의 이벤트를 핸들러 메서드에 바인딩하세요.
다음 디렉티브는 포인터가 호스트 엘리먼트 위에 있는 동안 엘리먼트를 강조하고, 포인터가 벗어나면 강조를 해제합니다.
highlight.directive.ts
import {Directive, ElementRef, inject} from '@angular/core';
@Directive({
selector: '[appHighlight]',
host: {
'(mouseenter)': 'onMouseEnter()',
'(mouseleave)': 'onMouseLeave()',
},
})
export class HighlightDirective {
private el = inject(ElementRef);
onMouseEnter() {
this.highlight('yellow');
}
onMouseLeave() {
this.highlight('');
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
host 프로퍼티는 mouseenter와 mouseleave 이벤트를 onMouseEnter()와 onMouseLeave() 메서드에 연결합니다.
이 메서드들은 호스트 엘리먼트의 배경색을 설정하는 highlight() 헬퍼를 호출합니다.
호스트 이벤트 바인딩은 호스트 엘리먼트에 바인딩하기를 참고하세요.
입력값 받기
컴포넌트와 마찬가지로 디렉티브도 input() 함수로 입력값을 받습니다.
입력 프로퍼티의 이름을 셀렉터와 같게 지정하면 하나의 바인딩으로 디렉티브를 적용하면서 값을 전달할 수 있습니다.
highlight.directive.ts
appHighlight = input('');
입력 프로퍼티를 시그널처럼 호출해서 값을 읽고, 색상을 지정하지 않으면 기본값을 사용하세요.
highlight.directive.ts
onMouseEnter() {
this.highlight(this.appHighlight() || 'red');
}
템플릿에서는 셀렉터에 값을 바인딩합니다.
입력 프로퍼티와 셀렉터의 이름이 같으므로 [appHighlight]는 디렉티브를 적용하고 값도 설정합니다.
여기서 바인딩한 color는 컴포넌트의 프로퍼티입니다.
app.component.html
<p [appHighlight]="color">Highlight me!</p>
app.component.ts
export class AppComponent {
color = '';
}
디렉티브에는 여러 입력 프로퍼티를 선언할 수 있습니다.
다음 디렉티브는 defaultColor 입력 프로퍼티를 추가하고, appHighlight, defaultColor, 마지막으로 red 순서로 사용할 색상을 결정합니다.
highlight.directive.ts
import {Directive, ElementRef, inject, input} from '@angular/core';
@Directive({
selector: '[appHighlight]',
host: {
'(mouseenter)': 'onMouseEnter()',
'(mouseleave)': 'onMouseLeave()',
},
})
export class HighlightDirective {
private el = inject(ElementRef);
defaultColor = input('');
appHighlight = input('');
onMouseEnter() {
this.highlight(this.appHighlight() || this.defaultColor() || 'red');
}
onMouseLeave() {
this.highlight('');
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
같은 엘리먼트에 두 입력 프로퍼티를 바인딩하세요.
defaultColor에는 동적인 표현식 대신 정적인 문자열을 전달하므로 대괄호가 필요하지 않습니다.
app.component.html
<p [appHighlight]="color" defaultColor="violet">Highlight me too!</p>
Angular 처리 중단하기: NgNonBindable
브라우저에서 Angular 표현식을 평가하지 않으려면 호스트 엘리먼트에 ngNonBindable을 추가하면 됩니다.
ngNonBindable은 문자열 바인딩, 디렉티브 동작, 템플릿의 바인딩을 비활성화합니다.
아래 예제 코드에서 {{ 1 + 1 }} 라는 표현식은 평가되지 않고 그대로 화면에 표시됩니다.
app.component.html
<p>Use ngNonBindable to stop evaluation.</p>
<p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p>
엘리먼트에 ngNonBindable을 지정하면 이 엘리먼트의 자식 엘리먼트의 바인딩이 중단됩니다.
하지만 ngNonBindable이 적용된 엘리먼트 자체에는 디렉티브 동작이 중단되지 않습니다.
아래 예제 코드처럼 구현하면, appHighlight 디렉티브는 여전히 동작하지만 표현식 {{ 1 + 1 }}는 평가되지 않습니다.
app.component.html
<h3>ngNonBindable with a directive</h3>
<div ngNonBindable [appHighlight]="'yellow'">
This should not evaluate: {{ 1 + 1 }}, but will highlight yellow.
</div>
그래서 어떤 엘리먼트를 포함하여 자식 엘리먼트들에 존재하는 문자열 바인딩, 프로퍼티 바인딩, 이벤트 바인딩을 모두 비활성화하려면, 해당 엘리먼트의 부모 엘리먼트에 ngNonBindable을 적용하면 됩니다.