본문 바로가기
Angular.js

[Angular] 참조변수 또는 템플릿 변수 (feat. ViewChild)

by 찬찬2 2023. 1. 11.

"템플릿 변수를 활용하면 템플릿 안에서 다른 영역에 있는 데이터를 참조할 수 있습니다. 그래서 템플릿 변수는 사용자가 입력한 내용에 반응하는 동작을 구현하거나, 애플리케이션 폼을 사용하기 좋게 튜닝할 때 주로 사용합니다." (공식문서)

 

사용법은 크게 두가지로 나뉜다. → 세 가지

1. HTML 태그 또는 컴포넌트 안에서 선언했을때 참조변수는 HTML 요소의 참조주소를 또는 컴포넌트 인스턴스의 참조주소를 가르킨다.

2. #variable ="디렉티브/컴포넌트" ← 와 같이 오른쪽에 값을 할당하는 형태인 경우, 첫 번째와 같이 참조변수는 디렉티브 또는 컴포넌트 인스턴스를 가르킨다.

3. (추가) 디렉티브 *ngTemplateOutlet 에 참조변수 templateRef 이름을 할당하면, templateRef 에 선언된 HTML template을 끌어와 사용할 수 있다. (그림1 참고)

TemplateRef란, 객체로써 <ng-template> 태그와 하위 콘텐츠들을 가르킨다.

 

그림1

 

<button #tooltip="matTooltip" (click)="tooltip.메서드">버튼</button>

*matTooltip 컴포넌트를 tooltip 참조변수에 할당, click 이벤트가 발생힐때 matTooltip 컴포넌트의 메서드에 접근할 수 있게된다.

 

템플릿 변수를 활용하면 템플릿 안에서 다른 영역에 있는 데이터를 참조할 수 있습니다.

이 말이 무슨 말일까?? 다른 영역이라 함은 HTML 템플릿 내부 또는 컴포넌트 내부를 가르킨다.

 

템플릿 변수의 참조 범위

템플릿 변수는 템플릿 안에서 자유롭게 참조할 수 있고, 템플릿을 넘어가는 범위에서는 참조할 수 없다.

 

중첩된 템플릿 안에서 접근하기

자식계층에서 선언된 템플릿 변수는 부모계층에서 참조가 가능하지만, 부모계층에서 선언된 템플릿 변수는 자식계층에서 참조할 수가 없다.

 

<input #ref1 type="text" [(ngModel)]="firstExample" />
<span *ngIf="true">Value: {{ ref1.value }}</span>

<input *ngIf="true" #ref2 type="text" [(ngModel)]="secondExample" />
<span>Value: {{ ref2?.value }}</span> <!-- 동작하지 않습니다. -->

*ngIf는 <ng-template>을 자식으로 만들어내내어 계층구조가 생깁니다.

 

그런데 말입니다...

 

Angular Mater UI를 사용하던 중 Sidenav 템플릿과 컴포넌트에서 아래와 같은 코드를 발견했는데...

 

    <mat-drawer
        class="w-full md:w-160 dark:bg-gray-900"
        [mode]="drawerMode"
        [opened]="false"
        [position]="'end'"
        [disableClose]="true"
        #matDrawer>
        <router-outlet></router-outlet>
    </mat-drawer>

 

mat-drawer라는 컴포넌트 안에 템플릿 변수 matDrawer가 선언되어 있었고, 

 

import { MatDrawer } from '@angular/material/sidenav';

export class DevicesComponent {
  @ViewChild('matDrawer', { static: true }) matDrawer: MatDrawer;
}

 

위와 같이 컴포넌트 클래스 내부에서 @ViewChild 데코레이터에 템플릿 변수 matDrawer와 바인딩이 되어 있는 듯한 모습을 하고 있는 것을 보았다. 그리고 공식문서에서 @ViewChild에 대해 찾아보니...

 

"Property decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated."

 

흠.. 포스트에서 발견한 글이 더 쉽게 와닿는거 같다. (아래)

 

"The ViewChild or ViewChildren decorators are used to Query and get the reference of the DOM element in the Component. ViewChild returns the first matching element and ViewChildren returns all the matching elements as a QueryList of items. We can use these references to manipulate element properties in the component."

 

즉, ViewChild를 통해 컴포넌트로 생성되는 DOM요소를 참조할 수 있다. ViewChild는 제일 처음 매칭되는 요소를 반환한다. 그리고 이렇게 매칭된 DOM요소를 조작(manipulate)할 수 있다.

 

ViewChild의 메타 데이터 프로퍼티 (arguments)

1. selector - 문자열 또는 문자열을 반환하는 함수, 컴포넌트

2. static - true : view가 최초로 렌더링 되었을때(change detection 전)

3. static - false : change detection이 일어날 때 마다

4. read - Use it to read the different token from the queried elements. (token이라면 DOM의 객체상태를 말하나?)

 


 

사용예제 #1 (ViewChild로 컴포넌트 제어하기)

import { Component } from '@angular/core';
 
@Component({
  selector: 'child-component',
  template: `<h2>Child Component</h2>
            current count is {{ count }}
    `
})
export class ChildComponent {
  count = 0;
 
  increment() {
    this.count++;
  }
  decrement() {
    this.count--;
  }
}

 

위 코드는 ChildComponent이다. increment, decrement 함수가 있다.

 

import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child.component';
 
@Component({
  selector: 'app-root',
  template: `
        <h1>{{title}}</h1>
        <p> current count is {{child.count}} </p>
        <button (click)="increment()">Increment</button>
        <button (click)="decrement()">decrement</button>
        <child-component></child-component>
        `
})
export class AppComponent {
  title = 'Parent calls an @ViewChild()';
  
  @ViewChild(ChildComponent, {static:true}) child: ChildComponent;
 
  increment() {
    this.child.increment();
  }
 
  decrement() {
    this.child.decrement();
  }
 
}

 

위 코드는 ChildComponent를 감싸고 있는 부모 컴포넌트이다. 부모 컴포넌트는 자식 컴포넌트가 가지고 있는 increment, decrement를 사용하기 위해 @ViewChild 데코레이터를 사용해 자식을 참조하였고, child 라는 변수에 할당하였다.

부모 컴포넌트는 자식 컴포넌트의 참조주소를 가지고 있는 child 변수를 이용해 this.child로 접근하여 자식 컴포넌트에 있는 increment, decrement 함수를 사용할 수 있게 되었다.

여기서 중요한 점은 접근하고자 하는 컴포넌트를 import 해왔다는 부분이다.

 

사용예제 #2 (ViewChild로 DOM요소 제어하기)

import { ViewChild, ElementRef } from @angular/core

export class ParentComponent () {
    @ViewChild("element") element: ElementRef;

    ngAfterViewInit(){
        console.log("element: ", element); // ElementRef { nativeElement: div }
    }
}

 

1번 예제와 같은 맥락이다.

 

사용예제 #3 - 템플릿 변수

<child-component #child></child-component>

 

템플릿 변수를 부모 컴포넌트에 선언한다.

 

 @ViewChild("child", { static: true }) child: ChildComponent;

 

그리고 부모 컴포넌트에서 위와 같이 ViewChild 데코레이터를 선언하면 된다.

 

사용예제 #4 - HTML 요소에 넣기 (Injecting HTML Element using ElementRef)

import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
 
@Component({
    selector: 'htmlelement',
    template: `
      <p #para>Some text</p>
    `,
})
export class HTMLElementComponent implements AfterViewInit {
 
    @ViewChild('para',{static:false}) para: ElementRef;
 
    ngAfterViewInit() {
      console.log(this.para.nativeElement.innerHTML);
      this.para.nativeElement.innerHTML="new text"
    }
}

 

위와 같이 ViewChild와 ElementRef, ngAfterViewInit을 이용해 HTML 요소에 접근할 수도있다. (참고링크)

 

사용예제 #5 - <ng-template> 넣기 "TemplateRef"

 

@Component({
  template: `
    <div>
      <ng-template #ref1> ... </ng-template>
      <ng-template #ref2> ... </ng-template>
    </div>
  `
})
export class MyComponent {
  @ViewChild('ref1') ref1: TemplateRef<any>;
  @ViewChild('ref2') ref2: TemplateRef<any>;
  ...
}

 

React와 비교했을때, DOM과 HTML 요소에 접근하는 방식은 useRef와 비슷하고, 부모가 자식 컴포넌트를 사용하는 방식은 React와 다르다. React는 자식이 부모 컴포넌트의 변수나 함수를 사용하기 위해서는 props로 전달받아야만 하기 때문이다.

 

※ 제일 처음 ViewChild에 대해 설명했을때 "ViewChild는 제일 처음 매칭되는 요소를 반환한다."라고 말했다. 결국 ViewChild는 "하나"만 반환한다. 그렇기 때문에 child 컴포넌트가 여러개이고, 모든 child 컴포넌트에 접근하기 위해서는 ViewChild가 아닌 ViewChildren을 사용해야 한다. 

 

실전 사용예제(공식문서)

댓글