Angular :: Component - 2

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';
}

이 글이 도움이 되었나요?

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