2019년 1월 9일 20:01

Angular 부모 컴포넌트에서 자식 요소로의 접근

부모 컴포넌트에서 자식 컴포넌트에 접근이 필요한 경우가 있는데, 이 때 템플릿 참조 변수를 사용해 접근할 수 있다.

부모 컴포넌트

<child #child></child> <button (click)="counter.handler()"></button>

자식 컴포넌트

export class ChildComponent {
  foo: any
  handler() {
    // something
  }
}

하지만 템플릿 참조변수를 사용할 수 없는 상황에서는 아래 4가지의 데코레이터를 사용하게 됩니다.

  • @ViewChild
  • @ViewChildren
  • @ContentChild
  • @ContentChildren

4가지 데코레이터를 통해 자식 컴포넌트 또는 네이티브 DOM 요소에 접근이 가능한데 자세 살펴보겠습니다.

@ViewChild

@ViewChild 는 탐색 조건에 해당되는 1개의 요소를 갖습니다.

@ViewChild(탐색 대상 클래스명) 프로퍼티명: 탐색 대상 클래스명;

@ViewChildren

@ViewChildren 은 탐색 조건에 해당되는 여러 개의 자식 요소를 모두 갖습니다. 그리고 그 타입은 QueryList 입니다.

@ViewChildren(탐색 대상 클래스명) 프로퍼티명: QueryList<탐색 대상 클래스명>;

QueryList

// query_list.d.ts
export declare class QueryList<T> {
  readonly dirty = true
  private _results
  readonly changes: Observable<any>
  readonly length: number
  readonly first: T
  readonly last: T

  map<U>(fn: (item: T, index: number, array: T[]) => U): U[]
  filter(fn: (item: T, index: number, array: T[]) => boolean): T[]
  find(fn: (item: T, index: number, array: T[]) => boolean): T | undefined
  reduce<U>(
    fn: (prevValue: U, curValue: T, curIndex: number, array: T[]) => U,
    init: U
  ): U
  forEach(fn: (item: T, index: number, array: T[]) => void): void
  some(fn: (value: T, index: number, array: T[]) => boolean): boolean
  toArray(): T[]
  toString(): string
  reset(res: Array<T | any[]>): void
  notifyOnChanges(): void
  /** internal */
  setDirty(): void
  /** internal */
  destroy(): void
}

QueryList 의 내부 구현부입니다. QueryList 의 _results 프로퍼티에 결과를 담아 놓고 있습니다. 그리고 자바스크립트 배열처럼 쓸 수 있도록 map, filter, find, reduce, forEach, some 등의 메서드를 사용할 수 있습니다. 또한, iterable 인터페이스를 구현하였기 때문에 for ... of 루프에도 사용가능하고 ngFor또한 사용 가능합니다.

네이티브 DOM 접근

@ViewChild@ViewChildren 데코레이터의 인자로 탐색 대상으로 지정된 '템플릿 참조 변수를 문자열 형태로 지정' 하게되면 네이티브 DOM 객체를 래핑한 ElementRef타입의 인스턴스를 획득할 수 있습니다.

<!-- ViewChild -->
<dom-tag #any></dom-tag>

<!-- ViewChildren -->
<dom-tag #any2></dom-tag>
<dom-tag #any3></dom-tag>
@ViewChild('any') 프로퍼티명: ElementRef;
@ViewChildren('any2, any3') 프로퍼티명: QueryList<ElementRef>;

Renderer2

직접 DOM 에 접근할 수 있지만 지양하는것이 좋습니다. 그 이유는 앵귤러의 컴포넌트 흐름때문인데 컴포넌트 클래스 상태 변경 -> 템플릿 뷰 상태 변경 -> DOM 변경 과 같이 작동합니다. 그런데 직접 DOM 을 조작하게 되면 이러한 로직이 꼬일 수 있습니다. 그리고 Angular ElementRef 에 따르면 직접 조작하게되면 보안에 취약할 수 있다고 합니다. 때문에 과거에는 Renderer, 현재는 Renderer2 를 사용해 조작하라고 권장하고 있습니다.https://angular.io/api/core/ElementRef

콘텐트 프로젝션

콘텐츠는 태그와 태그 사이에 있는 내용을 콘텐츠라 합니다.

<p>Hello</p>

여기서는 Hello 가 콘텐츠가 되는데, 부모컴포넌트에서 자식컴포넌트의 콘텐츠를 지정할 수 있습니다.

<child>
  <p>Contents</p>
</child>

여기서 <p>Contents</p> 자체가 콘텐츠가 됩니다. 그리고 자식 컴포넌트는 ngContent 디렉티브를 사용하여 전달받은 콘텐츠로 치환합니다.

<ng-content></ng-content>

싱글 프로젝션

부모 컴포넌트

<child>
  <p>Single Projection</p>
</child>

자식 컴포넌트

<ng-content></ng-content>

그러면 <ng-content></ng-content><p>Single Projection</p> 로 치환되어 표시됩니다.

멀티 프로젝션

네이밍있는 싱글 프로젝션이라 생각하면 편할 것 같습니다. select 어트리뷰트를 사용해 슬롯을 만들어줍니다.

자식 컴포넌트

<ng-content select="foo"></ng-content>
<ng-content select="bar"></ng-content>
<ng-content select=".baz-class"></ng-content>

부모 컴포넌트

<child>
  <foo>...</foo>
  <bar>...</bar>
  <div class="baz-class">...</div>
</child>

@ContentChild, @ContentChildren

ViewChild의 시작태그와 종료태그 사이에 있는 콘텐츠를 ContentChild 라 합니다. @ContentChild 는 탐색하려는 요소가 한개일 때 사용하고 @ContentChildren 은 탐색하려는 모든 요소를 가져오고 싶을 때 사용합니다.

// 부모 컴포넌트가 지정한 콘텐츠중 템플릿 참조변수 first 를 가진 콘텐츠를 가져옴
@ContentChild('first') firstChild: UserComponent;

// 부모 컴포넌트가 지정한 콘텐츠들 중 UserComponent 모두를 가져옴
@ContentChildren(UserComponent) children: QueryList<UserComponent>;

참고문서

©2022 heecheolman

Built with Gatsby