Component
지난 1편에 이어서 컴포넌트 관련된 내용 정리
자식 엘리먼트 참조
앵귤러에서는 자식 컴포넌트를 참조하는 2가지 방법(템플릿 지역 변수, ViewChild)을 제공한다.
템플릿 지역 변수
@Component({
selector: 'app-parent-component',
template: `
<app-child-component
#childComponent
[hasLiked]="hasLiked"
(onLike)="handleLikeClick()"
>
</app-child-component>
<button (click)="childComponent.onLike.emit()">Like</button>
`,
})
export class ParentComponent {
hasLiked = false
handleLikeClick() {
this.hasLiked = !this.hasLiked
}
}
위와같이 #ref
라는 키워드를 사용해서 템플릿 내에서 참조할 변수를 생성할 수 있다. onLike는 자식 변수에서 선언한 이벤트인데 위와같이 접근하여 부모에서 자식 컴포넌트의 모든 상태 및 메서드에 접근이 가능하다.
ViewChild
템플릿 지역 변수는 템플릿 내에서만 사용 가능하기 때문에 클래스 내부에서 직접 참조하려면 @ViewChild()
데코레이터를 활용하면 된다.
@Component({
selector: 'app-parent-component',
template: `
<app-child-component
[hasLiked]="hasLiked"
(onLike)="handleLikeClick()"
>
</app-child-component>
<button (click)="like()">Like</button>
`,
})
export class ParentComponent {
hasLiked = false
@ViewChild(ChildComponent)
private childComponentRef!: ChildComponent
like() {
this.childComponentRef.onLike.emit()
}
handleLikeClick() {
this.hasLiked = !this.hasLiked
}
}
만약 여러개의 동일한 자식 컴포넌트가 있을 때 ViewChild로 참조하면 어떻게 될까? 아래 코드는 자식 엘리먼트 내부에 hasLiked 상태를 따로 가지고 있도록 변경한 코드이다.
@Component({
selector: 'app-parent-component',
template: `
<app-child-component></app-child-component>
<app-child-component></app-child-component>
<button (click)="handleLikeClick()">
Like
</button>
`,
})
export class ParentComponent {
@ViewChild(ChildComponent)
private childComponentRef!: ChildComponent
handleLikeClick() {
this.childComponentRef.hasLiked = !this.childComponentRef.hasLiked
}
}
이제 자식 컴포넌트의 버튼을 누르면 각각의 상태가 변경되는데, 부모의 Like 버튼을 누르면 어떻게 될까? 결과는 첫번째 자식 컴포넌트의 상태만 변경된다. 모든 엘리먼트를 다 참조해서 수정하려면 다음과 같이 작성할 수 있다.
@Component({
selector: 'app-parent-component',
template: `
<app-child-component></app-child-component>
<app-child-component></app-child-component>
<button (click)="handleLikeClick()">
Like
</button>
`,
})
export class ParentComponent {
@ViewChildren(ChildComponent) children!: ChildComponent[];
handleLikeClick() {
this.children.forEach((child) => {
child.hasLiked = !child.hasLiked
});
}
}
위와같이 작성하면 부모의 Like 버튼을 누르면 모든 자식 컴포넌트의 상태가 변경된다.
컨텐츠 프로젝션
컴포넌트를 조합하여 랜더링하려면 컴포넌트 내부로 컴포넌트를 전달 해야하는 상황이 생긴다. 이를 컨텐츠 프로젝션이라고 한다. 앵귤러에서는 <ng-content>
태그로 감싸면 해당 영역을 전달받은 컨텐츠를 랜더링한다.
단일 슬롯 컨텐츠 프로젝션
@Component({
selector: 'app-card',
template: `
<div class="card">
<ng-content></ng-content>
</div>
`,
})
@Component({
selector: 'app-root',
template: `
<app-card>
<p>Card content</p>
</app-card>
`,
})
다중 슬롯 컨텐츠 프로젝션
카드에 공통적으로 들어가는 헤더가 있다면?
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="card-header">
<ng-content select="[card-header]"></ng-content>
</div>
<div class="card-body">
<ng-content></ng-content>
</div>
</div>
`,
})
@Component({
selector: 'app-root',
template: `
<app-card>
<h3 card-header>Card header</h3>
<p>Card content</p>
</app-card>
`,
})
select
에 선언하지 않은 값(예를들면 <h3 card-footer>Card footer</h3>
)을 어트리뷰트에 추가하는 경우에는 <ng-content></ng-content>
내부에 프로젝션 된다.
조건별 렌더링
문서에서는 <ng-container>
를 활용해서 조건별 렌더링은 권장하지 않고 있다. card-header가 존재할때만 해당 영역을 렌더링 해주고 싶었는데 아직까진 리액트처럼 근사한 방법은 찾지 못하여서 별도의 값을 전달해 주도록 하였다. 근사한 방법을 찾아봐야지...
@Component({
selector: 'app-card',
template: `
<div class="card">
<ng-template [ngIf]="hasHeader">
<div class="card-header">
<ng-content select="[card-header]"></ng-content>
</div>
</ng-template>
<div class="card-body">
<ng-content></ng-content>
</div>
</div>
`,
})
export class CardComponent {
@Input() hasHeader = true;
}
@Component({
selector: 'app-root',
template: `
<app-card [hasHeader]="false">
<p>Card content</p>
</app-card>
`,
})
export class AppComponent {
title = 'components';
}
Ghost