728x90
반응형

Service

이번 포스트는 Angular의 Service에 대해서 알아보겠습니다. 먼저 Service의 기본적인 사항들을 알아본 후 이를 통해 데이터를 공유하는 Service Mediator Pattern으로 넘어가면 될 듯 하네요.

이 Service는 Angular에만 존재하는 개념이 아닙니다. 객체지향 프로그래밍을 다뤄보신 분은 익히 들어본 개념입니다. 특히 Spring과 같은 Framework이나 MVC Pattern을 다뤄보신 분들이라면 쉽게 이해하실 수 있는 내용입니다.

우리는 Angular를 하고 있으니 여기에 맞춰 설명을 하자면 Component는 View를 표현하고 관리하는게 주된 역할입니다. 즉, 데이터를 받아와서 View에 출력한다던지 View의 값이 변경되면 그걸 또 어떻게 처리한다던지하는 View와 밀접한 로직을 Component class가 가지고 있게 됩니다.

만약 View를 처리하는 로직 이외의 별도의 로직이 필요하면 그 로직은 어디에 두는것이 좋을까요? 로그인 처리를 할때 필요한 인증로직이라던지 혹은 서버와의 데이터 통신을 위한 REST 서버의 호출같은 로직을 그냥 필요할 때마다 Component안에 집어 넣어서 처리하는게 좋을까요?

우리는 CBD(Component Based Development)를 하고 있습니다. 각각의 Component는 자신의 주된 관심사에 집중하게끔 코드를 작성해야 합니다. 객체지향설계에서 얘기하는 SRP(Single Responsibility Principle)을 생각하시면 됩니다. Component안에 다른 관심사가 존재하면 Component의 독립성이 보장되지 못하고 결국 중복 코드가 발생하며 Component의 재사용과 유지보수에 문제가 발생하게 되겠죠.

그래서 위에서 얘기한 별도의 로직들은 다른 곳에서 관리할 필요가 있습니다. Service라는 걸 이용해서 이 로직들을 작성하고 다른 Component에서 이 Service를 가져다가 사용하는 식으로 관리를 하면 SoC(Separation of Concern)원칙에 잘 들어맞을 거 같습니다.

이렇게 Component와 Service를 분리해서 작성하고 Component에서 Service를 사용하는 건 좋은데 사용할 때 문제가 하나 있습니다. Dependency라는게 생기는 거죠. 쉽게 단위 코드로 Component에서 Service를 사용하는 예를 한번 보죠. 아래는 Component class입니다.

MyService service = new MyService();
service.getUserAuth('moon9342');

pseudocode 입니다. Component class안에서 직접 Service 객체를 생성해서 이용하는 경우입니다. 이런 경우 우리 Component는 Service에 의존하게 됩니다. 이걸 Dependency Relationship(의존관계)이 존재한다 라고 표현하기도 합니다. 이 때 Component class의 입장에서 Service 객체를 Dependency라고 표현합니다.

이렇게 의존관계가 성립되면 Service가 변경되었을 때 우리 Component는 그에 따른 영향을 받을 수 밖에 없습니다. 연관관계가 강하게 성립되어서 서로 독립적으로 사용하는게 힘들어지는것이고 재사용이나 유지보수에 문제가 생기게 되겠네요.

이 문제를 해결하는 Design Pattern이 바로 DI(Dependency Injection)입니다. 우리 Service객체(Dependency)를 사용하는 객체인 Component에게 주입해서 사용하는 것입니다. 주입하는 방법은 일반적으로 constructor를 이용하는 방법과 setter를 이용하는 방법이 있는데 Angular는 constructor injection을 지원합니다.

즉, Component가 직접 Service를 new keyword로 생성하는 것이 아니라 Angular Framework이 Service를 Component가 사용할 있도록 Service객체를 생성해서 Component에게 넣어주는 방식입니다. 이걸 IoC(Inversion of Control)라고 합니다. Angular Framework은 IoC Container입니다.

이론적인 배경을 간단히 설명했으니 이제 Service를 우리 예제에 추가해보겠습니다. Angular application은 Module의 집합입니다. Module은 크게 Feature Module과 Shared Module이 있다는 얘기 혹시 기억하시나요? 어디에 Service를 생성하느냐 하는 문제인데 사실 case-by-case입니다. 특성상 여러 Feature Module에서 사용하는 공통 로직의 개념이면 따로 Shared Module을 만들어서 그 안에 Service를 포함시키는게 좋습니다. 하지만 우리 예제처럼 bookSearch Module에서만 사용할 생각이면 해당 Module안에 포함시키는게 더 좋은 선택이겠죠.


 Service 생성

다음의 코드를 이용해 우리 bookSearch Module에 서비스를 하나 추가합니다.

command 창을 열어서 다음과 같은 명령어를 실행시킵니다.

ng generate service HttpSupport

성공적으로 수행되면 현재 Module 폴더에 2개의 파일이 생성됩니다. 하나는 SPEC 파일이고 나머지 하나가 바로 Service 입니다.

다음은 http-support.service.ts 파일의 내용입니다.

import { Injectable } from '@angular/core';

@Injectable()
export class HttpSupportService {

  constructor() { }

}

주의해서 보셔야 하는 부분은 @Injectable decorator입니다. 해당 class가 다른 class에 주입(Injection)될 수 있다는걸 의미합니다. 아까도 설명했듯이 주입은 생성자를 이용하게 되고 주입과정은 Angular Framework이 담당합니다.

이제 이 안에 JSON 데이터를 가져오는 코드를 작성해야 합니다. 우리 예제의 list-boxComponent는 View를 rendering할 때 HttpClient를 이용해 JSON데이터를 가져와 Material Table로 화면에 바로 출력합니다. 이 부분을 변경해야겠죠. search-boxComponent에서 Search! 버튼을 클릭하면 HttpClient를 이용해서 데이터를 가져와서 그 데이터를 list-box가 사용할 수 있도록 처리해야 합니다.

원래는 Back End 프로그램도 하나 작성해서 RESTful 서비스 하는걸 예로 들어야 하는데 서버쪽 프로그램이 없으니 그냥 JSON 파일로 부터 데이터를 받는걸로 처리했습니다.

다음은 수정된 http-support.service.ts 파일의 내용입니다.

import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";

interface IBook {
  bauthor: string;
  bdate: string;
  btranslator: string;
  bpublisher: string;
  btitle: string;
  bprice: number;
  bisbn: string;
  bimgurl: string;
}

@Injectable()
export class HttpSupportService {

  books: IBook[];
  constructor(private http: HttpClient) { }

  getJsonData() {
    this.http.get<IBook[]>('assets/data/book.json')
        .subscribe(res => {
           this.books = res;
           console.log(this.books);
        });
  }
}

interface IBook도 저런식으로 코드마다 등장해서는 안되겠죠. 원래 따로 빼서 관리해야 합니다. 하지만 예제를 좀 이해하기 쉽도록 그냥 중복해서 썻습니다. ^^;;

constructor(private http: HttpClient) { }

생성자로 인자가 하나 들어옵니다. 사실 이것도 HttpClient 타입의 객체가 우리 서비스 안으로 Injection되는 것입니다. 생성자에 인자를 받으면서 Access Modifier를 이용하면 class안에 속성으로 자동 지정됩니다. 여기서는 private으로 Injection된 HttpClient 객체를 받았습니다.

getJsonData() method가 호출되면 Injection받은 HttpClient 객체를 이용해서 파일로부터 JSON 데이터를 읽어들인 후 console에 정상적으로 읽었는지 출력합니다.


 Service Injection

위에서 생성한 Service 객체를 search-box Component에 Injection한 후 사용해 보겠습니다.

다음은 search-box.component.ts 파일의 내용입니다.

import {
  Component, OnInit,
  Input, Output, EventEmitter
} from '@angular/core';

import { HttpSupportService } from "../http-support.service";

@Component({
  selector: 'app-search-box',
  templateUrl: './search-box.component.html',
  styleUrls: ['./search-box.component.css'],
  providers: [
    HttpSupportService
  ]
})
export class SearchBoxComponent implements OnInit {

  _bookCategory: string;
  //@Input() bookCategory:string;
  //@Input('bookCategory') mySelected:string;

  @Input()
  set bookCategory(value: string) {
    if( value != null ) {
      // 추가적인 작업이 들어올 수 있습니다.
      this._bookCategory = 'category: ' +value;
    } else {
      this._bookCategory = value;
    }

  }

  @Output() searchEvent = new EventEmitter();

  keyword = null;

  constructor(private httpSupportService:HttpSupportService) { }

  ngOnInit() {
  }

  setKeyword(keyword: string): void {
    this.keyword = keyword;
    this.searchEvent.emit({
      keyword : `${this.keyword}`,
      category: `${this._bookCategory.replace('category: ','')}`
    });

    this.httpSupportService.getJsonData();

  }

  inputChange(): void {

  }
}

기존 코드에서 변경된 부분을 살펴보면

import { HttpSupportService } from "../http-support.service";

기본적으로 import는 시켜줘야 사용할 수 있겠지요.

constructor(private httpSupportService:HttpSupportService) { }

constructor를 이용해 Service가 Injection되었습니다.

this.httpSupportService.getJsonData();

Injection받은 Service의 method를 호출하는 부분입니다.

@Component({
  selector: 'app-search-box',
  templateUrl: './search-box.component.html',
  styleUrls: ['./search-box.component.css'],
  providers: [
    HttpSupportService
  ]
})

Angular Framework에 어떤 class가 Injection이 되는지 알려줘야 합니다. Component의 Metadata부분에 providers를 이용해 처리해야 합니다.

실행해보면 정상적으로 console에 JSON데이터가 출력되는걸 확인하실 수 있습니다.


 Injector

기본적으로 Angular Framework은 dependency객체를 어떻게 생성해야 하는지 알지 못합니다. 그래서 우리가 Component의 Metadata를 이용해서 providers에 그 정보를 명시했었지요. 이 정보를 근간으로 Injector가 의존객체를 생성하고 주입합니다.

정리하자면 Component가 생성될 때 Angualr는 Injection에 필요한 객체를 Injector에 요청합니다. 이 Injector는 이미 생성한 객체들을 담고 있는 Container를 유지하고 있는데 이 안에 객체가 있으면 바로 주입하고 그렇지 않으면 의존객체를 생성한 후 주입하게 됩니다.

그림으로 표현하면 다음과 같습니다.

angular-injector

( 이미지 출처 : https://angular.io/guide/architecture )

여기서 주의해야 할 점이 있는데 각각의 Component 각자 하나씩의 Injector를 가지고 있습니다. Component는 tree형식으로 구성되니 Injector 역시 tree형태로 구성이 되게됩니다. 만약 Injection요청에 대한 내용이 현재 Component의 providers부분에 명시되어 있지 않으면 부모 Component의 providers에서 검색하게 됩니다. 이렇게 부모로 타고 올라가면서 의존객체를 찾게 되는것이죠. 만약 상위 Component에서 의존객체를 생성해 놓았으면 하위 Component에서 따로 선언하지 않아도 사용이 가능합니다.

또한 Component의 providers에 등록해 놓을 수도 있지만 Module의 providers에도 등록할 수 있습니다. 이런 경우 해당 Module안에 있는 모든 Component들이 해당 의존모듈을 사용할 수 있게 되겠네요. 최상위 Component인 Root Component가 가지고 있는 Root Injector는 Application 전역에서 사용가능한 의존모듈을 가지고 있게 되겠네요.


 Provider

위에서 설명했듯이 Module안에 providers로 등록한 의존객체는 Module안에서 사용이 가능합니다. Component에서 등록한 의존객체는 자신과 자식 Component에서 사용이 가능하지요.

이렇게 보면 Module과 Component에 등록하는게 크게 차이가 없어 보이지만 Module에 등록하는 경우 의존객체는 하나의 객체가 생성되서 사용됩니다. 즉, Singleton 형태로 사용된다는 것이죠. 반면 Component에 등록된 의존객체는 해당 Component가 생성될 때 마다 의존객체가 따로 생성되게 됩니다.

따라서 정보공유를 목적으로 하는 Service Mediator Pattern을 이용할 경우 일반적으로 Module에 의존객체를 등록해서 사용하는것이 좋습니다.

이 provider에 대해서 조금만 더 알아보도록 하죠.

Component안에서 의존객체를 등록하려면 다음의 코드를 이용합니다.

@Component({
  selector: 'app-search-box',
  templateUrl: './search-box.component.html',
  styleUrls: ['./search-box.component.css'],
  providers: [
    HttpSupportService
  ]
})

우리는 지금 의존객체라는 표현을 쓰면서 객체만이 주입되는식으로 표현했는데 실제 객체뿐만 아니라 Value도 주입할 수 있습니다. 일단 먼저 의존객체를 주입하는 방식에 대해서 코드를 조금만 상세히 표현해 보겠습니다. 위의 코드는 사실 밑의 코드의 축약형 입니다.

@Component({
  selector: 'app-search-box',
  templateUrl: './search-box.component.html',
  styleUrls: ['./search-box.component.css'],
  providers: [
    {
      provide: HttpSupportService,    // 데이터 타입
      useClass: HttpSupportService    // 실제 객체를 생성하기 위해 필요한 class
    }
  ]
})

provide의 값과 useClass의 값이 같을 경우 축약형으로 표현할 수 있습니다. provide는 만들어지는 객체의 데이터 타입입니다. useClass는 실제 객체를 생성하기 위해 사용되는 class명이구요. 당연히 두개가 틀릴 수 있습니다. interface를 이용하거나 duck typing을 이용하면 서로 다른 데이터 타입과 class를 사용할 수 있습니다.

duck typing에 대해서는 여기를 클릭하시면 간단한 내용을 확인하실 수 있습니다.

이번에는 의존객체가 아닌 고정값을 주입하는 방법에 대해서 알아보겠습니다. 일반적인 예는 configuration 값을 주입받는 경우입니다. 간단하게 환경설정파일을 하나 만들어서 그 안에 configuration내용을 채워놓고 그 값을 주입받아 보겠습니다.

command 창을 열어 다음의 명령을 실행해서 하나의 class를 생성합니다. 현재 command 창의 working directory는 search-box Component의 위치입니다.

ng generate class jsonConfig

json-config.ts 파일이 생성됩니다. 해당 파일에 다음과 같이 우리가 사용하는 JSON 파일에 대한 경로와 파일명을 설정정보로 입력합니다.

export class JsonConfig {
  url: string;
  name: string;
}

export const JSON_DATA_CONFIG: JsonConfig = {
  url: 'assets/data/',
  name: 'book.json'
};

다음은 search-box.component.ts 파일의 내용입니다.

import {
  Component, OnInit,
  Input, Output, EventEmitter
} from '@angular/core';
import { HttpSupportService } from "../http-support.service";
import { JSON_DATA_CONFIG, JsonConfig } from "./json-config";


@Component({
  selector: 'app-search-box',
  templateUrl: './search-box.component.html',
  styleUrls: ['./search-box.component.css'],
  providers: [
    {
      provide: HttpSupportService,
      useClass: HttpSupportService
    },
    {
      provide: JsonConfig,
      useValue: JSON_DATA_CONFIG
    }
  ]
})
export class SearchBoxComponent implements OnInit {

  _bookCategory: string;
  //@Input() bookCategory:string;
  //@Input('bookCategory') mySelected:string;

  @Input()
  set bookCategory(value: string) {
    if( value != null ) {
      // 추가적인 작업이 들어올 수 있습니다.
      this._bookCategory = 'category: ' +value;
    } else {
      this._bookCategory = value;
    }

  }

  @Output() searchEvent = new EventEmitter();

  keyword = null;

  constructor(private httpSupportService:HttpSupportService,
              private jsonConfig:JsonConfig) { }

  ngOnInit() {
  }

  setKeyword(keyword: string): void {
    this.keyword = keyword;
    this.searchEvent.emit({
      keyword : `${this.keyword}`,
      category: `${this._bookCategory.replace('category: ','')}`
    });

    this.httpSupportService.getJsonData(this.jsonConfig.url, this.jsonConfig.name);

  }

  inputChange(): void {

  }
}

기존에 비해 몇가지 사항이 달라졌습니다. Value를 Injection받을 때 어떻게 처리해야 하는지를 유심히 보시면 됩니다.

service의 method를 호출할 때 주입값을 가지고 method를 호출하기 때문에 service의 코드도 변경해야 합니다.

다음은 http-support.service.ts 파일의 내용입니다.

import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";

interface IBook {
  bauthor: string;
  bdate: string;
  btranslator: string;
  bpublisher: string;
  btitle: string;
  bprice: number;
  bisbn: string;
  bimgurl: string;
}

@Injectable()
export class HttpSupportService {

  books: IBook[];
  constructor(private http: HttpClient) { }

  getJsonData(url:string, name:string) {
    this.http.get<IBook[]>(`${url}${name}`)
        .subscribe(res => {
           this.books = res;
           console.log(this.books);
        });
  }
}

마지막으로 한가지가 더 남아있습니다. 의존객체를 생성할 때 객체를 그대로 사용하는게 아니라 특정 로직을 거쳐 의존객체를 생성해 사용할 수 있습니다. 객체지향에서 나왔던 Factory Pattern 생각하시면 됩니다. 이 부분은 여기서 따로 설명하지는 않겠습니다.


 Optional Dependency

Optional Dependency는 의존객체의 주입이 필수가 아니라는 것을 의미합니다. @Optionaldecorator를 이용하면 의존객체가 존재하지 않더라도 프로그램 오류가 나지 않습니다.

단순히 생성자에서 의존객체를 주입받을 때 @Optional decorator를 명시하시면 됩니다. 물론 의존객체가 들어오지 않을때의 로직처리는 해 주어야 합니다.

constructor(private httpSupportService:HttpSupportService,
            @optional private jsonConfig:JsonConfig) { }

이번 포스트에서는 Angular의 Service에 대해서 알아보았습니다. 기본적인 Service의 사용방법을 먼저 숙지하신 후 이 Service를 이용해 데이터를 공유하는 방법으로 넘어가면 될 듯 보입니다. 다음 포스트는 Service Mediator Pattern을 이용한 Component간 데이터 공유에 대해서 알아보도록 하겠습니다.

728x90
반응형
728x90
반응형

Content Projection

이번 포스트는 과거 AngularJS에서 transclusion이라고 불리었던 Content Projection에 대해서 알아보겠습니다. 쉽게 말하자면 부모 Component가 자식 Component에게 template을 전달해 줄 수 있는 기능입니다. 이 역시 우리 예제에서는 필요없는 부분이지만 간단하게 내용을 추가해서 알아보겠습니다.

다음은 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 #resultStatus class="mb-0 text-white lh-100">Search Result : {{searchTitle}}</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">
          {{ category.viewValue }}
        </mat-option>
      </mat-select>
    </mat-form-field>
    <button mat-raised-button color="primary"
            (click)="clearCondition()">검색 초기화</button>
    <button mat-raised-button color="primary"
            (click)="changeDOM()">DOM 직접 변경</button>
  </div>

  <div>
    <app-search-box [bookCategory]="displayCategoryName"
                    (searchEvent)="changeTitleBar($event)">
      <p>Content Projection!</p>
      <p>First Paragraph</p>
      <p>Second Paragraph</p>
    </app-search-box>
  </div>
  <div>
    <app-detail-box></app-detail-box>
  </div>
  <div>
    <app-list-box></app-list-box>
  </div>
</div>

기존에 비해 변경된 부분만 보시면 됩니다. 위의 코드 중


<app-search-box [bookCategory]="displayCategoryName"
                (searchEvent)="changeTitleBar($event)">
  <p>Content Projection!</p>
  <p>First Paragraph</p>
  <p>Second Paragraph</p>
</app-search-box>

부분을 보면 하위 Component를 포함시키면서 3개의 <p> Element를 전달한 것을 보실 수 있습니다. 이렇게 부모 Component가 자식 Component에게 특정 template을 전달해 줄 수 있는 기능이라고 생각하시면 됩니다. 자식 Component인 search-box.component.html은 다음과 같이 작성합니다.


<div class="example-container">
  <mat-toolbar #toolbar class="search-toolbar-style">
    Search Keyword : {{keyword}}
    <ng-container *ngIf="_bookCategory != null">
      ( {{_bookCategory}} )
    </ng-container>
  </mat-toolbar>
  <mat-form-field>
    <input matInput #inputKeyword placeholder="Search Keyword"
           [(ngModel)]="keyword" (ngModelChange)="inputChange()">
  </mat-form-field>
  <button mat-raised-button color="warn"
          (click)="setKeyword(inputKeyword.value)">Search!</button>
  <ng-content></ng-content>
</div>

다른 부분은 다 동일하고 맨 마지막에 <ng-content></ng-content> directive가 보입니다. 이 directive가 부모 Component가 전달해 준 template으로 치환되게 됩니다.

다음에는 Service에 대해서 알아본 후 이를 이용한 데이터 공유 방법인 Service Mediator Pattern에 대해서 알아보도록 하겠습니다.

728x90
반응형

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

Angular Service Mediator Pattern  (0) 2018.08.28
Angular Service  (0) 2018.08.28
Angular @ViewChild 데이터공유  (0) 2018.08.28
Angular @Output 데이터공유  (0) 2018.08.28
Angular @Input 데이터공유  (0) 2018.08.28
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
728x90
반응형

 @Output Decorator

이번 포스트는 자식 Component에서 부모 Component로 데이터를 전달하기 위한 @Outputdecorator에 대해서 알아보겠습니다.

자식 Component에서 부모 Component로 데이터를 전달하기 위해서는 EventEmitter를 이용한 이벤트 처리를 하셔야 합니다. 즉, 자식 Component에서 발생한 event를 부모 Component가 event bidning을 이용해 데이터를 받는 방식입니다.

코드레벨에서 알아보죠. 이전에 @Input decorator를 설명하면서 사용했던 예제를 좀 수정해서 사용하겠습니다.

아래는 search-box.component.ts 내용입니다.

import { Component, OnInit,
         Input, Output, EventEmitter } from '@angular/core';

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

  _bookCategory: string;
  //@Input() bookCategory:string;
  //@Input('bookCategory') mySelected:string;

  @Input()
  set bookCategory(value: string) {
    if( value != null ) {
      // 추가적인 작업이 들어올 수 있습니다.
      this._bookCategory = 'category: ' +value;
    } else {
      this._bookCategory = value;
    }

  }

  @Output() searchEvent = new EventEmitter();

  keyword = null;

  constructor() { }

  ngOnInit() {
  }

  setKeyword(keyword: string): void {
    this.keyword = keyword;
    this.searchEvent.emit({
      keyword : `${this.keyword}`,
      category: `${this._bookCategory.replace('category: ','')}`
    });
  }

  inputChange(): void {

  }
}

부모 Component에게 이벤트를 전달하기 위해 다음의 코드로 EventEmitter 객체를 생성하고 @Output decorator를 이용했습니다. 부모 Component는 searchEvent 이름으로 event binding 해야합니다.

@Output() searchEvent = new EventEmitter();

자식 Component에서 Search! 버튼을 눌렀을 때 setKeyword() method가 호출되는데 이 안에서 searchEvent에 대한 이벤트를 발생시킵니다. 그러면서 부모 Component에게 전달할 데이터를 인자로 넣어줍니다.

this.searchEvent.emit({
    keyword : `${this.keyword}`,
    category: `${this._bookCategory.replace('category: ','')}`
});

이제 부모 Component에서 어떻게 event binding을 이용해서 데이터를 받는지만 살펴보면 됩니다.

아래는 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 : {{searchTitle}}</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">
          {{ category.viewValue }}
        </mat-option>
      </mat-select>
    </mat-form-field>
  </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>

주의해서 보셔야 할 부분은

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

입니다. event binding을 이용해서 searchEvent 이벤트가 발생하면 changeTitleBar()method를 호출하고 인자를 받아서 처리하고 있네요. 인자 받는 방식도 유의해서 보셔야 합니다.


<h5 class="mb-0 text-white lh-100">Search Result : {{searchTitle}}</h5>

interpolation을 이용해 searchTitle 속성의 값을 View에 출력하고 있네요. 아마 changeTitleBar() method안에서 내용이 결정될 듯 합니다.

마지막으로 book-search-main.component.ts 파일입니다.

import { Component, OnInit } from '@angular/core';

@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} )`;
  }
}

기본적인 개념은 이미 설명했으니 코드레벨에서 한번 천천히 살펴보시면 충분히 이해할 수 있을 듯 보입니다.

여기까지 작업한 내용을 실행해 정상적으로 동작하는지 확인하시면 될 듯 합니다.

자식 Component에서 부모 Component로 Event를 이용해서 데이터를 어떻게 전달하는지에 대해서 간단하게 살펴보았습니다.

다음 포스트는 부모 Component에서 자식 요소에 접근할 때 또 다른 방법으로 어떤 방법이 있는지 알아보도록 하겠습니다.

728x90
반응형

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

Angular Content Projection 데이터공유  (0) 2018.08.28
Angular @ViewChild 데이터공유  (0) 2018.08.28
Angular @Input 데이터공유  (0) 2018.08.28
Angular Material Table  (0) 2018.08.28
Angular 예제 따라하기  (0) 2018.08.28
728x90
반응형

Component Data Sharing

이번 포스트는 Component 간의 데이터 공유에 대해서 알아보고 그 내용을 기반으로 우리 mySearchProject의 도서검색 부분을 완성해 나가도록 하겠습니다.

최종적으로 완성된 프로그램은 여기를 클릭하시면 실행시켜 보실 수 있습니다.

실행시켜 보시면 이전에 비해 세가지 기능이 추가되었습니다.

  • 도서 검색 시 도서 종류(국내도서, 국외도서, 국내외도서)에 대한 Filtering이 가능합니다.
  • 키워드 입력 후 Search버튼을 클릭하면 해당 키워드에 대한 책만 list-box에 출력됩니다.
  • list-box에 출력된 책 중 하나를 선택하면 해당 책에 대한 세부내역을 detail-box에 출력합니다.

이 기능들을 구현하려면 Component간의 데이터 공유 방법을 아셔야 합니다. Component간 데이터를 공유하는 방법은 여러가지가 있는데 하나씩 살펴보면서 우리 코드에 적용해 보겠습니다.

그럼 천천히 한번 살펴보기로 하죠.


 @Input Decorator

이전에 View의 포함관계를 설명하면서 Component Tree에 대한 언급을 한 적이 있습니다. Component간의 부모-자식 관계가 성립되면 서로간의 데이터 연결통로가 생성됩니다. 이를 통해 부모 Component와 자식 Component간의 데이터 공유가 이루어질 수 있습니다.

먼저 부모 Component에서 자식 Component로 데이터를 전달하는 방법에 대해서 알아보죠.

만약 부모 Component가 사용자 입력양식을 가지고 있다면 Client에 의해서 사용자 입력양식의 상태값이 변경될 수 있고 그 상태값를 자식 Component와 공유할 필요가 있습니다. 우리 예제로 설명하자면 상위 Component인 book-search-main Component에서 Client가 설정한 도서 종류가 하위 Component인 search-box Component에 전해져야 제대로 된 검색을 수행할 수 있다는 말입니다.

이런 경우 부모 Component는 property binding을 이용해 자식 Component에게 데이터를 전달해 줄 수 있습니다. 이렇게 전달된 데이터는 @Input decorator에 의해서 자식 Component에서 사용될 수 있습니다.

우리 예제를 수정해서 Client가 Select Box에서 선택한 도서 종류 정보가 하위 Component인 search-box Component와 공유되는지 확인해 보겠습니다.

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">
          {{ category.viewValue }}
        </mat-option>
      </mat-select>
    </mat-form-field>
  </div>

  <div>
    <app-search-box [bookCategory]="displayCategoryName"></app-search-box>
  </div>
  <div>
    <app-detail-box></app-detail-box>
  </div>
  <div>
    <app-list-box></app-list-box>
  </div>
</div>

위의 코드에서 다음의 코드를 주의해서 보시면 됩니다.


<mat-select placeholder="도서종류"
            #bookCategorySelect
            [(ngModel)]="selectedValue"
            (ngModelChange)="changeValue(bookCategorySelect.value)">
    <mat-option *ngFor="let category of bookCaterory"
                [value]="category.value">
                {{ category.viewValue }}
    </mat-option>
</mat-select>

이전에 배웠던 Tempalte Reference Variable과 양방향 바인딩을 이용해 Client가 도서 종류를 변경하면 changeValue() method가 호출됩니다.

이 method는 book-search-main.component.ts안에 정의되어 있어야 하겠죠.

import { Component, OnInit } from '@angular/core';

@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: '국외도서'}
  ];

  constructor() { }

  ngOnInit() {
  }

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

method의 하는일을 보니 Client가 선택한 도서 종류를 가지고 displayCategoryName이라는 속성의 값을 변경하고 있습니다.

이 displayCategoryName 속성이 바로 자식 Component인 search-box Component에게 전달되는 데이터입니다.

다시 위쪽의 book-search-main.component.html의 내용을 보면 아래와 같은 코드가 있습니다.

<div>
    <app-search-box [bookCategory]="displayCategoryName"></app-search-box>
</div>

search-box Component에 property binding을 이용해 bookCategory라는 이름으로 displayCategoryName 속성을 바인딩해 놓은걸 확인하실 수 있습니다.

이제 search-box.component.ts의 내용을 보죠

import { Component, OnInit, Input } from '@angular/core';

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

  @Input() bookCategory:string;

  keyword = null;

  constructor() { }

  ngOnInit() {
  }

  setKeyword(keyword: string): void {
    this.keyword = keyword;
  }

  inputChange(): void {

  }
}

@Input decorator를 볼 수 있습니다. @Input decorator를 이용하기 위해서 import를 해 줘야 하는것도 잊지 마시구요.

@Input() bookCategory:string;

위의 코드처럼 bookCategory라는 이름으로 부모 Component가 property binding으로 전달해준 데이터를 받을 수 있습니다.

이 속성을 View에 interpolation을 이용해서 출력하면 될 듯 합니다.

다음은 View에 rendering되는 search-box.component.html 입니다.


<div class="example-container">
  <mat-toolbar class="search-toolbar-style">
    Search Keyword : {{keyword}}
    <ng-container *ngIf="bookCategory != null">
      ( {{bookCategory}} )
    </ng-container>
  </mat-toolbar>
  <mat-form-field>
    <input matInput #inputKeyword placeholder="Search Keyword"
           [(ngModel)]="keyword" (ngModelChange)="inputChange()">
  </mat-form-field>
  <button mat-raised-button color="warn"
          (click)="setKeyword(inputKeyword.value)">Search!</button>
</div>

Toolbar부분에 {{keyword}}와 함께 {{bookCategory}}를 이용해서 속성과 binding시킨 걸 확인할 수 있습니다. 만약 bookCategory값이 null이면 출력되지 않게끔 built-in directive를 이용해 처리했습니다.

위의 코드에서 보듯이 자식 Component인 select-box Component는 자신에게 데이터를 주는 부모 Component가 어떤 Component인지는 알 필요가 없습니다. 단지 전달된 데이터를 사용할 수 있도록 해주는 property의 이름과 data type만이 필요할 뿐이죠. Component간의 Loosely Coupling을 유지하면서 데이터를 공유할 수 있습니다.

기본적인 @Input decorator를 사용하는 방법에 대해 설명했는데 몇개의 응용이 있습니다.

우리는 부모 Component의 book-search-main.component.html에서 property binding을 이용해 bookCategory라는 이름의 property를 사용했습니다. 이를 사용하기 위해서 자식 Component인 search-box Component에서 역시 같은 이름으로 사용했구요. 만약 다른이름으로 사용하실려면 아래와 같이 처리하시면 됩니다.

@Input('bookCategory') mySelected:string;

bookCategory라는 이름의 property 대신 mySelected property를 사용할 수 있습니다. interpolation 역시 mySelected으로 사용해야겠죠.

지금까지의 예는 모두 부모 Component가 전달해 준 데이터를 그대로 가져다 사용하는 방식입니다. 만약 부모 Component가 전달해 준 데이터를 가공해서 자식 Component에서 사용하려면 어떻게 해야 할까요?

setter를 이용하면 이 작업을 할 수 있습니다. 우리의 코드가 이렇게 바뀌겠네요.

import { Component, OnInit, Input } from '@angular/core';

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

  //@Input() bookCategory:string;
  //@Input('bookCategory') mySelected:string;
  _bookCategory: string;  

  @Input()
  set bookCategory(value: string) {
    if( value != null ) {
      // 추가적인 작업이 들어올 수 있습니다.
      this._bookCategory = 'category: ' +value;
    } else {
      this._bookCategory = value;
    }

  }

  keyword = null;

  constructor() { }

  ngOnInit() {
  }

  setKeyword(keyword: string): void {
    this.keyword = keyword;
  }

  inputChange(): void {

  }
}

사용되는 setter의 이름과 부모 Component가 property binding하는 property의 이름이 같아야 합니다. interpolation은 _bookCategory으로 변경되어야 하겠네요.

한가지 추가적으로 기억하셔야 할 점은 이렇게 부모 Component가 자식 Component에게 데이터를 전달해 줄 때 이 방식이 call-by-value방식이 아닌 call-by-reference방식이라는 것입니다. 즉, 우리의 예제에서 부모 Component와 자식 Component가 둘 다 bookCategory를 reference하고 있는 형태입니다. 이렇게 연결된 상태에서 부모 Component가 해당 property의 값을 변경시키면 그 값을 자식 Component가 공유하고 있으므로 변경된 값을 그 즉시 사용할 수 있는 것이죠.

이렇게 생각하면 자식 Component가 공유되고 있는 property의 값을 변경하면 그 변경 내용이 부모 Component에게 영향을 미쳐야 합니다. 하지만 실제로 코드 작업을 해 보면 그렇지 않다는 것을 확인하실 수 있습니다. 왜 이런 현상이 발생할까요? 만약 자식 Component에서 변경된 값이 부모 Component에게 영향을 주게끔 처리하면 나중에 이 공유데이터의 변화를 예측하기 힘들어지게 됩니다. 데이터의 변경을 tracking하기 힘들어진다는 것이죠. 지금은 간단한 경우이니 별 문제가 안되지만 프로그램이 커지게 되면 이런 데이터의 공유 문제가 프로그램의 구현과 디버깅을 힘들게 하는 원인이 됩니다.

Angular는 Stateful Component와 Stateless Component의 개념이 있습니다. Stateful Component는 다른말로 Smart Component 라고도 하는데 이 Component는 데이터의 정보를 변경하거나 저장할 수 있습니다. 하지만 Dumb Component라고 불리는 Stateless Component는 단지 상태 정보를 참조만 해서 이용할 수 있습니다. 우리의 예제에서 상위 Component인 book-search-main Component는 Stateful Component입니다. 반면 자식 Component인 search-box Component는 Stateless Component이구요. 그렇기 때문에 자식 Component에서 공유된 변수에 대한 변경을 해 주어도 상위 Component에 영향을 미치지 않게 되는 것입니다. 조금 어려운 개념인데 이 Stateful과 Stateless에 대해 조금 더 알고싶으시면 여기를 참조하시면 됩니다.

쉽게 말하자면 @Input decorator를 이용하면 부모 Component에서 자식 Component에게 데이터를 전달할 수 있지만 그 반대는 허용되지 않는군요. 이 문제를 해결하기 위해 @Outputdecorator를 사용할 수 있습니다. 즉, 자식 Component에서 변경된 사항을 부모 Component에게 전달하는 방법이 따로 있다는 것이죠. 다음 포스트에서는 @Output decorator에 대해서 알아보도록 하겠습니다.

728x90
반응형

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

Angular @ViewChild 데이터공유  (0) 2018.08.28
Angular @Output 데이터공유  (0) 2018.08.28
Angular Material Table  (0) 2018.08.28
Angular 예제 따라하기  (0) 2018.08.28
SPA형식의 Web Application  (0) 2018.08.28

+ Recent posts