728x90
반응형

 부모 Component의 직접적인 자식 요소 제어

이번 포스트는 부모 Component에서 자식 요소에 직접 접근하는 방법에 대해서 알아보겠습니다. 이전 포스트에서 @Input decorator를 이용해 부모 Component에서 자식 Component로 데이터를 전달하는 방법에 대해서 알아보았는데 이번에는 약간 다릅니다.

부모 Component는 자식 Component 객체뿐만 아니라 자식으로 포함된 Directive에 직접 접근할 수 있고 또한 Component가 Rendering하는 View자체에 직접 접근할 수 있습니다.

하지만 이런 접근 방법이 항상 좋은건 아닙니다. 오히려 좋지 않은 현상이 발생하게 됩니다. 예를 들어 Component가 직접적으로 DOM에 접근해서 제어하는 코드를 작성한다고 가정해 보죠. 일단 간단하게 프로그램을 구현할 수 있으나 나중에 Component의 View가 변경되면 Component에서 처리하는 부분도 당연히 그에 맞게 바뀌어야 합니다. Component의 재사용성과 유지보수성에 문제가 생길 여지가 있습니다.

그렇기 때문에 이런 직접적인 접근방식은 꼭 필요한 경우가 아니면 지양하는 것이 좋습니다.

그럼 천천히 한번 알아보도록 하죠.


 @ViewChild, @ViewChildren Decorator

부모 Component template안에 위치한 모든 자식 요소들을 ViewChild라고 합니다. 이 ViewChild안에는 자식 Component 객체뿐만 아니라 Component가 Rendering하는 View의 DOM 그리고 Directive가 포함됩니다.

자식 Component객체에 직접 접근하는 방법부터 살펴보도록 하겠습니다.

자식 Component 객체에 직접 접근하려면 @ViewChild decorator를 이용하시면 됩니다. 조건에 부합되는 객체 1개를 찾게되고 그에 대한 property를 지정해서 사용할 수 있습니다. 만약 @ViewChildren을 이용하면 조건에 부합되는 객체를 모두 찾게 되고 QueryList 형태로 객체들의 집합을 얻을 수 있습니다. QueryList는 실제 배열이 아니기 때문에 toArray()method를 이용해 배열을 얻어내 이용할 수 있습니다.

그럼 간단한 예를 가지고 알아보도록 하죠.

부모 Component에 초기화버튼을 하나 만들어서 해당 버튼을 누르면 Client가 선택한 도서 종류와 입력된 키워드를 초기화 시키는 작업을 해 보도록 하겠습니다.

먼저 초기화버튼을 만들어야 하니 book-search-main.component.html부터 수정해야 합니다.

<div class="bookSearch-outer">
  <div class="d-flex align-items-center p-3 my-3 text-white-50 bg-purple rounded box-shadow">
    <img class="mr-3" src="assets/images/search-icon.png" alt="" width="48" height="48">
    <div class="lh-100">
      <h5 class="mb-0 text-white lh-100">Search Result : </h5>
    </div>
  </div>

  <div class="example-container">
    <mat-form-field>
      <mat-select placeholder="도서종류"
                  #bookCategorySelect
                  [(ngModel)]="selectedValue"
                  (ngModelChange)="changeValue(bookCategorySelect.value)">
        <mat-option *ngFor="let category of bookCaterory"
                    [value]="category.value">
          
        </mat-option>
      </mat-select>
    </mat-form-field>
    <button mat-raised-button color="primary"
            (click)="clearCondition()">검색 초기화</button>
  </div>

  <div>
    <app-search-box [bookCategory]="displayCategoryName"
                    (searchEvent)="changeTitleBar($event)"></app-search-box>
  </div>
  <div>
    <app-detail-box></app-detail-box>
  </div>
  <div>
    <app-list-box></app-list-box>
  </div>
</div>

검색 초기화 버튼을 생성하고 해당 버튼을 클릭하면 clearCondition() method가 호출되도록 처리했습니다.

다음은 부모 Component인 book-search-main.component.ts 파일입니다. clearCondition() method를 작성해야하고 해당 method안에서 자신의 검색에 관련된 사항을 초기화하고 자식 Component를 찾아 자식 Component의 property를 초기화시키는 작업을 진행합니다.

import {Component, OnInit,
        ViewChild, ViewChildren, QueryList } from '@angular/core';
import { SearchBoxComponent } from "../search-box/search-box.component";

@Component({
  selector: 'app-book-search-main',
  templateUrl: './book-search-main.component.html',
  styleUrls: ['./book-search-main.component.css',
  './offcanvas.css']
})
export class BookSearchMainComponent implements OnInit {

  selectedValue = null;
  displayCategoryName = null;

  bookCaterory = [
    {value: 'all', viewValue: '국내외도서'},
    {value: 'country', viewValue: '국내도서'},
    {value: 'foreign', viewValue: '국외도서'}
  ];

  searchTitle = null;

  constructor() { }

  ngOnInit() {
  }

  changeValue(category: string): void {
    for(let element of this.bookCaterory ) {
      if(element.value == category) {
        this.displayCategoryName = element.viewValue;
      }
    }
  }

  changeTitleBar(searchInfo) : void {
    this.searchTitle = `${searchInfo.keyword} ( ${searchInfo.category} )`;
  }

  @ViewChild(SearchBoxComponent) searchComp: SearchBoxComponent;
  @ViewChildren(SearchBoxComponent) searchCompArr: QueryList<SearchBoxComponent>;

  clearCondition(): void {
    this.selectedValue = null;
    this.searchTitle = null;
/*
    @ViewChild를 사용할 경우
    this.searchComp._bookCategory = null;
    this.searchComp.keyword = null;
*/
    // @ViewChildren을 사용할 경우
    this.searchCompArr.toArray()[0]._bookCategory = null;
    this.searchCompArr.toArray()[0].keyword = null;
  }
}

부모 Component와 자식 Component가 데이터를 공유하는게 아니라 부모 Component가 직접 자식 Component 객체를 제어하는 방식입니다.


 Component가 Rendering하는 View의 DOM에 직접 접근

@ViewChild와 @ViewChildren을 이용하면 자식 Component의 객체뿐 아니라 Component가 rendering하는 View의 DOM에 직접 접근할 수 있습니다. 이전에 나왔던 Template Reference Variable을 이용해서 Component가 DOM에 접근하는 것이죠.

우리 예제에 딱히 필요하진 않지만 이해를 돕기 위해 버튼 하나를 더 추가해 어떻게 사용하는지 살펴보겠습니다.

book-search-main.component.html을 수정해 버튼을 하나 더 추가합니다.

...
...
<h5 #resultStatus class="mb-0 text-white lh-100">Search Result : </h5>
...
...
...
    <button mat-raised-button color="primary"
            (click)="changeDOM()">DOM 직접 변경</button>
...
...            

일부만 표시했습니다. 결과를 표시하는 영역에 Template Reference Variable #resultStatus을 지정했습니다. 그리고 버튼을 하나 추가했구요. 해당 버튼을 클릭하면 changeDOM() method가 호출되겠네요.

다음은 book-search-main.component.ts 파일 내용입니다.

import {Component, OnInit,
        ViewChild, ViewChildren, QueryList,
        ElementRef } from '@angular/core';
import { SearchBoxComponent } from "../search-box/search-box.component";


@Component({
  selector: 'app-book-search-main',
  templateUrl: './book-search-main.component.html',
  styleUrls: ['./book-search-main.component.css',
  './offcanvas.css']
})
export class BookSearchMainComponent implements OnInit {

  selectedValue = null;
  displayCategoryName = null;

  bookCaterory = [
    {value: 'all', viewValue: '국내외도서'},
    {value: 'country', viewValue: '국내도서'},
    {value: 'foreign', viewValue: '국외도서'}
  ];

  searchTitle = null;

  constructor() { }

  ngOnInit() {
  }

  changeValue(category: string): void {
    for(let element of this.bookCaterory ) {
      if(element.value == category) {
        this.displayCategoryName = element.viewValue;
      }
    }
  }

  changeTitleBar(searchInfo) : void {
    this.searchTitle = `${searchInfo.keyword} ( ${searchInfo.category} )`;
  }

  @ViewChild(SearchBoxComponent) searchComp: SearchBoxComponent;
  @ViewChildren(SearchBoxComponent) searchCompArr: QueryList<SearchBoxComponent>;

  clearCondition(): void {
    this.selectedValue = null;
    this.searchTitle = null;
/*
    @ViewChild를 사용할 경우
    this.searchComp._bookCategory = null;
    this.searchComp.keyword = null;
*/
    // @ViewChildren을 사용할 경우
    this.searchCompArr.toArray()[0]._bookCategory = null;
    this.searchCompArr.toArray()[0].keyword = null;
  }

  @ViewChild('resultStatus') resultToolbar: ElementRef;

  changeDOM(): void {
    this.resultToolbar.nativeElement.onclick = function() {
      alert('DOM을 직접 제어할 수 있어요!!');
    };
    this.resultToolbar.nativeElement.innerHTML = "클릭해보세요!!";
  }

}

아래부분에 resultStatus Template Reference Variable을 이용해서 해당 Element의 Reference를 획득하는 부분을 잘 보시면 됩니다. 이렇게 ElementRef type의 객체를 획득하면 nativeElement 속성으로 직접 제어할 수 있습니다.

이번 포스트에서는 @ViewChild와 @ViewChildren을 이용해 자식 Component의 객체를 직접 제어하거나 rendering된 View의 DOM에 직접 접근해서 제어하는 방법에 대해서 살펴보았습니다. 다음 포스트는 Angular에서 Content Projection이라고 불리는 부분에 대해서 살펴보도록 하겠습니다.

728x90
반응형

'Web Programming > Angular - TypeScript' 카테고리의 다른 글

Angular Service  (0) 2018.08.28
Angular Content Projection 데이터공유  (0) 2018.08.28
Angular @Output 데이터공유  (0) 2018.08.28
Angular @Input 데이터공유  (0) 2018.08.28
Angular Material Table  (0) 2018.08.28

+ Recent posts