6. 어트리뷰트, 클래스, 스타일 바인딩
어트리뷰트 바인딩을 활용하면 엘리먼트 어트리뷰트의 값을 직접 지정할 수 있다. 이 문법을 활용하면 웹 접근성을 향상시킬 수 있고, 애플리케이션 스타일을 동적으로 조정할 수 있으며, CSS 클래스 여러 개와 CSS 스타일 여러 개를 한 번에 적용할 수 있다.
1. 어트리뷰트 바인딩하기
엘리먼트 프로퍼티 값을 프로퍼티 바인딩으로 할당할 수 있다면 이 방법이 가장 좋다. 하지만 엘리먼트 프로퍼티를 바인딩할 수 없는 경우가 있다. 어트리뷰트 바인딩은 이런 경우에 사용한다.
ARIA와 SVG에는 어트리뷰트만 존재한다. 그래서 엘리먼트 프로퍼티가 존재하지 않기 때문에 프로퍼티 값을 지정할 수도 없다. 이런 경우에는 어트리뷰트 바인딩을 사용하는 수밖에 없다.
2. 문법
어트리뷰트 바인딩 문법은 프로퍼티 바인딩 문법과 비슷하지만, 대과호([]
) 안에 엘리먼트 프로퍼티를 지정하지 않고 attr.
접두사를 붙인 후에 어트리뷰트 이름을 지정한다. 등호 오른쪽에는 어트리뷰트에 지정할 값을 문자열을 반환하는 표현식으로 연결하면 된다.
Note
- 표현식이
null
이나undefined
로 평가되면 Angular는 해당 어트리뷰트를 자체를 제거한다.
3. ARIA 어트리뷰트 바인딩하기
어트리뷰트 바인딩을 사용하는 목적 중 가장 중요한 것은 ARIA 어트리뷰트를 설정하는 것이다:
<!-- 웹 접근성 향상을 위해 ARIA 어트리뷰트를 지정할 수 있습니다. -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
4. colspan
바인딩하기
어트리뷰트 바인딩은 테이블 엘리먼트의 colspan
어트리뷰트를 바인딩할 때도 사용한다. 이 어트리뷰트를 바인딩하면 테이블의 모습을 동적으로 변경할 수 있다. 데이터의 모습에 따라 행/열의 병합 개수를 조절할 때 사용한다.
<td>
엘리먼트에 colspan
어트리뷰트를 바인딩하려면 이렇게 하면 된다:
1] [attr.colspan]
문법으로 colspan
어트리뷰트에 접근한다.
2] [attr.colspan]
에 표현식을 연결한다.
아래 예제는 colspan
어트리뷰트에 1 + 1
라는 표현식을 연결하는 예제 코드이다.
<!-- 표현식 평가 결과는 colspan=2 입니다. -->
<tr>
<td [attr.colspan]="1 + 1">One-Two</td>
</tr>
그러면 <td>
어트리뷰트가 2열로 구성된다.
Note
- 프로퍼티의 이름과 어트리뷰트 이름이 다른 경우가 가끔 있다.
<td>
엘리먼트를 생각해보면colspan
은 어트리뷰트이며 대문자 "S"가 들어간colSpan
은 프로퍼티이다.- 어트리뷰트를 바인딩하려면 반드시 소문자 "s"가 들어간
colspan
을 사용해야 한다.
5. class
어트리뷰트 바인딩하기
엘리먼트의 class
어트리뷰트를 바인딩하면 해당 엘리먼트에 CSS 클래스를 적용하거나 제거할 수 있다.
1) CSS class
하나 바인딩하기
클래스를 하나만 바인딩하려면 [class.sale]="onSale"
라는 문법처럼 class
뒤에 마침표(.
)를 붙이고 원하는 CSS 클래스를 지정하면 된다. 그러면 onSale
라는 표현식이 참으로 평가될 때 sale
클래스의 엘리먼트에 적용하며, 표현식이 거짓으로 평가되면 sale
클래스를 엘리먼트에서 제거한다. 표현식이 undefined
로 평가되면 오류가 발생한다.
2) CSS 클래스 여러 개 바인딩하기
클래스를 여러 개 바인딩하려면 [class]="클래스-표현식"
이라는 문법을 사용하면 된다. 이 때 표현식은 다음 형식 중 하나를 사용할 수 있다:
- 클래스 이름을 공백해서 문자열로 지정하기
- 클래스 이름을 키로 하고 참/거짓으로 평가되는 값으로 구성된 객체
- 클래스 이름 배열
객체 형태를 사용하면 해당 객체 중 참으로 평가되는 클래스만 엘리먼트에 적용된다.
Note
object
,Array
,Map
,Set
과 같은 객체로 클래스를 바인딩할 때는 이 객체의 인스턴스가 변경되어야 클래스 목록이 변경된다.- 객체 인스턴스는 그대로고 프로퍼티 값만 변경되면 반영되지 않는다.
같은 클래스 이름이 여러 곳에서 바인딩되면 스타일 지정 우선순위에 따라 적용 여부가 결정된다.
클래스 바인딩 문법을 요약하면 이렇다.
바인딩 타입 | 문법 | 입력값 타입 | 입력값 예시 |
---|---|---|---|
클래스 하나 바인딩 | [class.sale]="onSale" |
boolean \| undefined \| null |
true , false |
클래스 여러 개 바인딩 | [class]="classExpression" |
string , Record<string, boolean \| undefined \| null> , Array<string> |
"my-class-1 my-class-2 my-class-3" , {foo: true, bar: false} , ['foo', 'bar'] |
6. 스타일 어트리뷰트 바인딩하기
스타일 어트리뷰트를 바인딩하면 엘리먼트의 스타일을 동적으로 변경할 수 있다.
1) 스타일 하나 바인딩하기
스타일을 하나만 바인딩하려면 [style.width]="width"
처럼 style
접두사 뒤에 마침표(.
)를 붙이고 원하는 CSS 스타일 프로퍼티 이름을 지정하면 된다. Angular는 바인딩된 표현식을 평가하고 프로퍼티 값으로 지정하는데, 보통 이 값은 문자열 타입이지만 em
이나 %
와 같은 단위를 붙이면 숫자를 사용할 수도 있다.
Note
- 스타일 프로퍼티 이름은 대시-케이스(dash-case)와 캐멀 케이스(camelCase) 중 하나를 사용할 수 있다.
2) 스타일 여러 개 바인딩하기
스타일 여러 개를 한 번에 조작하려면 [style]="스타일-표현식"
이라는 문법처럼 [style]
어트리뷰트를 바인딩하면 된다. 이 때 styleExpression
은 다음 형식 중 하나를 사용할 수 있다:
- 문자열로 구성된 스타일:
"width: 100px; height: 100px; background-color: cornflowerblue;"
- 스타일 이름을 키로 하고 그 값으로 스타일을 지정한 객체:
{width: '100px', height: '100px', backgroundColor: 'cornflowerblue'}
[style]
에 배열을 바인딩하는 방식은 지원하지 않는다.
Note
[style]
에 객체를 바인딩하면, 객체의 인스턴스가 변경되어야 스타일이 제대로 변경된다.- 객체 인스턴스는 그대로고 프로퍼티 값만 변경되면 반영되지 않는다.
3) 한 번에 여러 스타일을 바인딩하는 예제
@Component({
selector: "app-nav-bar",
template: ` <nav [style]="navStyle">
<a [style.text-decoration]="activeLinkStyle">Home Page</a>
<a [style.text-decoration]="linkStyle">Login</a>
</nav>`,
})
export class NavBarComponent {
navStyle = "font-size: 1.2rem; color: cornflowerblue;";
linkStyle = "underline";
activeLinkStyle = "overline";
/* . . . */
}
같은 스타일 어트리뷰트를 여러 번 바인딩하는 경우에는 Angular 스타일 우선순위에 따라 최종 스타일이 결정된다.
아래 표를 참고한다.
바인딩 타입 | 문법 | 입력값 타입 | 입력값 예시 |
---|---|---|---|
스타일 하나 바인딩 | [style.width]="width" |
string \| undefined \| null |
"100px" |
단위와 함께 스타일 하나 바인딩 | [style.width.px]="width" |
number \| undefined \| null |
100 |
스타일 여러 개 바인딩 | [style]="styleExpression" |
string , Record<string, string \| undefined \| null> |
"width: 100px; height: 100px" , {width: '100px', height: '100px'} |
Note
NgStyle
디렉티브를 사용하는 것보다는 직접[style]
처럼 바인딩하는 것이 더 좋다.[style]
이라고 사용하는 것에 비해NgStyle
디렉티브를 사용하는 것이 특별히 유리한 점이 없다고 판단되면, 이후 Angular 버전에는 스타일 바인딩 성능향상을 위해NgStyle
디렉티브가 제거될 수 있다.
7. 스타일 우선순위
HTML 엘리먼트 하나에 디렉티브가 여러 개 적용되는 상황이라면 CSS 클래스 목록이나 스타일 값이 여러 번 바인딩될 수 있다.
이 때 같은 클래스 이름이나 스타일 프로퍼티가 여러 번 바인딩되면 Angular는 우선순위에 따라 엘리먼트에 적용될 클래스와 스타일을 결정한다.
스타일 우선순위는 다음과 같다(높은 순서부터 낮은 순서로):
1] 템플릿 바인딩
- 프로퍼티 바인딩 (ex.
<div [class.foo]="hasFoo">
,<div [style.color]="color">
) - 맵 바인딩 (ex.
<div [class]="classExpr">
,<div [style]="styleExpr">
) - 정적 값 (ex.
<div class="foo">
,<div style="color: blue">
)
2] 디렉티브 호스트 바인딩
- 프로퍼티 바인딩 (ex.
host: {'[class.foo]': 'hasFoo'}
orhost: {'[style.color]': 'color'}
) - 맵 바인딩 (ex.
host: {'[class]': 'classExpr'}
,host: {'[style]': 'styleExpr'}
) - 정적 값 (ex.
host: {'class': 'foo'}
,host: {'style': 'color: blue'}
)
3] 컴포넌트 호스트 바인딩
- 프로퍼티 바인딩 (ex.
host: {'[class.foo]': 'hasFoo'}
,host: {'[style.color]': 'color'}
) - 맵 바인딩 (ex.
host: {'[class]': 'classExpr'}
,host: {'[style]': 'styleExpr'}
) - 정적 값 (ex.
host: {'class': 'foo'}
,host: {'style': 'color: blue'}
)
그리고 더 구체적인 클래스/스타일 바인딩을 사용할수록 우선순외가 높다.
그래서 [class.foo]
라고 바인딩하는 방식은 [class]
라고 바인딩하는 것보다 우선순위가 높으며, 마찬가지로 [style.bar]
라고 바인딩하는 방식은 [style]
라고 바인딩하는 것보다 우선순위가 높다.
<h3>기본 순서</h3>
<!-- `class.special`이 `classExpression`로 지정되는 `class`보다 우선순위가 높습니다. -->
<div [class.special]="isSpecial" [class]="classExpression">Some text.</div>
<!-- `style.border`가 `styleExpression`로 지정되는 `border`보다 우선순위가 높습니다. -->
<div [style.border]="border" [style]="styleExpression">Some text.</div>
클래스/스타일의 우선순위를 결정하는 규칙은 여러 소스에서 바인딩을 하는 경우에 적용된다. 템플릿에서 직접 엘리먼트에 바인딩하거나, 디렉티브가 호스트 바인딩하는 경우, 컴포넌트가 호스트 바인딩하는 경우가 이런 경우에 해당된다.
이 중에서 템플릿 바인딩의 우선순위가 가장 높다. 엘리먼트에 직접, 명시적으로 적용되었기 때문이다.
디렉티브 호스트 바인딩은 그 다음으로 우선순위가 높다. 디렉티브는 템플릿에 여러 번 사용될 수 있기 때문에 템플릿에서 직접 바인딩하는 것보다는 우선순위가 낮다.
디렉티브는 컴포넌트의 기능을 확장하기도 한다. 그래서 컴포넌트 호스트 바인딩의 우선순위가 가장 낮다.
<h3>소스별 우선순위</h3>
<!-- `class.special` 템플릿 바인딩이 `dirWithClassBinding`이나 `comp-with-host-binding`가 바인딩하는 `special` 클래스보다 우선순위가 높습니다.-->
<comp-with-host-binding
[class.special]="isSpecial"
dirWithClassBinding
></comp-with-host-binding>
<!-- `style.color` 템플릿 바인딩이 `dirWithStyleBinding`이나 `comp-with-host-binding`가 바인딩하는 `color` 프로퍼티보다 우선순위가 높습니다.. -->
<div>
<comp-with-host-binding
[style.color]="color"
dirWithStyleBinding
></comp-with-host-binding>
</div>
<h3>동적 vs 정적</h3>
<!-- `classExpression`이 동적으로 처리되기 때문에 `class`보다 우선순위가 높습니다. -->
<div class="special" [class]="classExpression">Some text.</div>
<!-- `styleExpression`이 동적으로 처리되기 때문에 `style="border: dotted darkblue 3px"`보다 우선순위가 높습니다 -->
<div style="border: dotted darkblue 3px" [style]="styleExpression">
Some text.
</div>
<div class="readability">
<comp-with-host-binding dirWithHostBinding></comp-with-host-binding>
</div>
<app-my-input-with-attribute-decorator
type="number"
></app-my-input-with-attribute-decorator>
추가로, 바인딩 문법은 정적 어트리뷰트 할당 방식보다 우선순위가 높다.
아래 코드에서 class
와 [class]
의 우선순위가 비슷할 것 같지만, [class]
라고 바인딩한 것이 동적으로 할당되기 때문에 정적으로 할당한 것보다 우선순위가 높다.
<h3>동적 vs 정적</h3>
<!-- `classExpression`이 동적으로 처리되기 때문에 `class`보다 우선순위가 높습니다. -->
<div class="special" [class]="classExpression">Some text.</div>
<!-- `styleExpression`이 동적으로 처리되기 때문에 `style="border: dotted darkblue 3px"`보다 우선순위가 높습니다 -->
<div style="border: dotted darkblue 3px" [style]="styleExpression">
Some text.
</div>
1) 낮은 우선순위로 적용하기
높은 우선순위에서 undefined
값을 지정하면 낮은 우선순위로 위임(delegate)할 수 있다. 이 때 null
값을 지정하면 스타일 프로퍼티가 제거되기 때문에, 낮은 우선순위로 넘기려면 undefined
값을 지정해야 한다.
템플릿이 이렇게 구성되었다고 하자:
dirWithHostBinding
디렉티브와 comp-with-host-binding
컴포넌트는 모두 호스트 바인딩으로 [style.width]
라는 표현을 사용한다. 이런 경우에 dirWithHostBinding
에서 undefined
값을 바인딩하면 width
프로퍼티는 comp-with-host-binding
호스트 바인딩 결과로 결정된다. dirWithHostBinding
에서 null
값을 바인딩하면 width
프로퍼티가 엘리먼트에서 제거된다.
8. 어트리뷰트 값을 의존성으로 주입하기
호스트 엘리먼트에 있는 어트리뷰트 값을 컴포넌트나 디렉티브에서 받아야 하는 경우가 있다. <button>
엘리먼트나 <input>
엘리먼트에 적용된 type
값을 참조하는 디렉티브가 있다고 하자.
이런 경우에 @Attribute
데코레이터를 사용하면 HTML 어트리뷰트에 지정된 값을 컴포넌트/디렉티브 생성자에 의존성으로 주입받을 수 있다.
Note
- 의존성으로 주입되는 값은 의존성 주입이 일어난 시점의 HTML 어트리뷰트 값이다.
- 이후에 변경되는 HTML 어트리뷰트 값은 전달되지 않는다.
이 예제에서 app.component.html
은 The type of the input is: number와 같이 표시된다.
이 방식은 RouterOutlet
디렉티브에도 활용할 수 있다. 라우팅 영역의 이름을 @Attribute
데코레이터로 받아오는 방식으로 활용하면 된다.
Note
- 어트리뷰트 값이 변경되는 것을 감지하려면
@Input()
를 사용해야 한다. @Attribute()
는 컴포넌트/디렉티브 생성자에서 HTML 어트리뷰트의 값을 의존성으로 주입받는 용도로 사용한다.