2-1. Angular의 구성요소 - Component

컴포넌트


컴포넌트는 Angular의 핵심 구성 요소로서 Angular 애플리케이션은 컴포넌트를 중심으로 구성됩니다.
컴포넌트의 역할은 애플리케이션의 화면을 구성하는 뷰(View)를 생성하고 관리하는 것이고
Angular는 컴포넌트를 조립하여 하나의 완성된 애플리케이션을 작성합니다.
컴포넌트는 컴포넌트 클래스와 템플릿으로 구성되는데,

  • 컴포넌트 클래스는 애플리케이션 데이터와 로직을 처리하고
  • 템플릿은 화면에 표시할 HTML을 정의합니다.

Angular 컴포넌트는 컴포넌트 클래스에 @Component 데코레이터를 사용해서
컴포넌트에 대한 메타데이터를 지정하면서 템플릿도 함께 지정하는 형태를 띄고 있습니다.

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

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.scss']
})

export class HelloComponent {
  // 여기서 로직과 데이터를 관리
}

👉 (참고) 컴포넌트 메타데이터

  • selector: 컴포넌트 인스턴스가 DOM 트리의 어떤 자리에 위치할지 CSS 셀렉터로 지정합니다.
    Angular는 템플릿 HTML에 사용된 컴포넌트 셀렉터를 기준으로 컴포넌트의 인스턴스를 생성하기 때문에 컴포넌트에 CSS 셀렉터 를 지정해야 합니다.

  • templateUrl: 컴포넌트의 HTML 템플릿을 외부 파일에 정의할 때, 이 템플릿 파일의 위치를 지정합니다.
    템플릿을 인라인으로 구성하려면 이 프로퍼티 대신 template 프로퍼티를 사용하면 됩니다.

  • providers: 컴포넌트가 생성될 때 의존성으로 주입되는 서비스의 프로바이더를 지정합니다.

View는 컴포넌트 메타데이터에서 지정하는 외부 템플릿 파일이나
인라인 템플릿이 컴포넌트 코드와 연결되면서 정의됩니다.
템플릿을 외부 파일에서 불러올지 컴포넌트 안에 포함시킬지는 메타데이터 설정에 의해 결정됩니다.
그리고 의존성으로 주입받아야 하는 서비스가 있다면 이 내용도 메타데이터에 지정할 수 있습니다.

컴포넌트 트리

Angular 애플리케이션은 분할된 컴포넌트로 구성되기 때문에
컴포넌트 간에는 컴포넌트 트리로 표현되는 '부모-자식' 관계가 형성됩니다.
컴포넌트 간의 부모-자식 관계는 데이터와 이벤트가 왕래하는 상태 정보 흐름의 통로가 되며,
이를 위해 상태 공유가 이루어지기 때문에 컴포넌트 간의 부모-자식 관계는
Angular 애플리케이션에서 중요한 의미를 갖습니다.
따라서 설계 시점부터 화면을 어떠한 컴포넌트 단위로 분할할 것인지에 대한 검토가 필요합니다.


컴포넌트의 기본 동작 구조

컴포넌트의 기본 동작 구조를 알아보기 위해
@Component 데코레이터의 templateUrl 프로퍼티에 설정된 템플릿을 살펴봅시다.

<h1>
  Welcome to {{ title }}!
</h1>

템플릿은 컴포넌트의 뷰를 정의하기 위해 HTML과 Angular 고유의 템플릿 문법으로 작성합니다.
{{ title }}은 템플릿 문법 중 하나인 인터폴레이션으로, 컴포넌트 클래스의 데이터를 템플릿에 바인딩합니다.

이러한 방식을 데이터 바인딩이라고 합니다다.

컴포넌트는 데이터 바인딩에 의해 템플릿과 컴포넌트 클래스의 데이터를 유기적으로 연계합니다.
기본적인 동작 구조는 아래와 같습니다.


컴포넌트 간 통신방법

1. 부모-자식 관계일때

1) @Input 데코레이터 (부모 → 자식)

자식 컴포넌트/디렉티브에 있는 특정 프로퍼티가
부모 컴포넌트/디렉티브에서 값을 받는다는 것을 지정하는 데코레이터입니다.
프로퍼티 바인딩으로 전달한 데이터는 자식 컴포넌트에서 @Input 데코레이터로 받을 수 있습니다.

child

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

import { Hero } from './hero';

@Component({
  selector: 'app-hero-child',
  template: `
    <h3>{{hero.name}} says:</h3>
    <p>I, {{hero.name}}, am at your service, {{masterName}}.</p>
  `
})
export class HeroChildComponent {
  @Input() hero: Hero;
  @Input('master') masterName: string; 
	// masterName 프로퍼티를 외부에서 바인딩 할 때 'master'라는 이름으로 사용하기 위한 선언
}

parent

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

import { HEROES } from './hero';

@Component({
  selector: 'app-hero-parent',
  template: `
    <h2>{{master}} controls {{heroes.length}} heroes</h2>
    <app-hero-child *ngFor="let hero of heroes"
      [hero]="hero"
      [master]="master">
    </app-hero-child>
  `
})
export class HeroParentComponent {
  heroes = HEROES;
  master = 'Master';
}

👉
parent는 *ngFor를 사용해서 배열에 있는 항목마다 child를 만드는데,
각 컴포넌트를 만들때마다 master 문자열 프로퍼티를 자식 컴포넌트의 master로 연결하고,
반복되는 hero 인스턴스를 자식 컴포넌트의 hero 프로퍼티로 바인딩 합니다.

2) @Output 데코레이터 (자식 → 부모)

@Output 데코레이터는 자식의 데이터를 부모에 전달하는데 사용합니다.
주로 이벤트를 바인딩하여 사용하며, 자식 컴포넌트에서 발생한 이벤트를 부모 컴포넌트가 이벤트 바인딩해서 데이터를 전달받게 됩니다.

자식 컴포넌트에서 어떤 이벤트가 발생하면 이 이벤트는 EventEmitter 타입으로 지정한 프로퍼티를 통해 부모 컴포넌트에게 보낼 수 있습니다. 부모 컴포넌트는 이 이벤트를 바인딩해서 원하는 로직을 실행하면 됩니다.

예제를 통해서 살펴봅시다.

child

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

@Component({
  selector: 'app-voter',
  template: `
    <h4>{{name}}</h4>
    <button (click)="vote(true)"  [disabled]="didVote">Agree</button>
    <button (click)="vote(false)" [disabled]="didVote">Disagree</button>
  `
})
export class VoterComponent {
  @Input()  name: string;
  @Output() voted = new EventEmitter<boolean>();
  didVote = false;

  vote(agreed: boolean) {
    this.voted.emit(agreed);
    this.didVote = true;
  }
}

parent

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

@Component({
  selector: 'app-vote-taker',
  template: `
    <h2>Should mankind colonize the Universe?</h2>
    <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
    <app-voter *ngFor="let voter of voters"
      [name]="voter"
      (voted)="onVoted($event)">
    </app-voter>
  `
})
export class VoteTakerComponent {
  agreed = 0;
  disagreed = 0;
  voters = ['Narco', 'Celeritas', 'Bombasto'];

  onVoted(agreed: boolean) {
    agreed ? this.agreed++ : this.disagreed++;
  }
}

이 예제에서 버튼을 클릭하면 자식 컴포넌트로 불리언 타입의 데이터를 전달합니다.
그리고 parent의 onVoted() 함수가 이벤트 객체를 인자로 받아서 agree와 disagree 카운터를 갱신합니다.

이 때 전달되는 이벤트 객체는 템플릿에서 $event라는 이름으로 접근할 수 있으며,
템플릿에서 이벤트 핸들러 함수에 인자로 전달하기 때문에 컴포넌트 클래스 코드에서 이 이벤트 객체를 활용할 수 있습니다.

  • 예제2(장바구니)

child

    @Component({
    	selector: 'app-child',
    	template: `
    	<p>
    	{{item}}
    	<button (click)="addToCart(item)">추가</button>
    	</p>
    `
    })
    export class ChildComponent {
    	@Input() item;
    	@Output() pushItem = new EventEmitter<string>();
    	addToCart(item:string) {
    		this.pushItem.emit(item);
    	}
    }

parent

    @Component({
      selector: 'app-parent',
      template: `
    	<h1>식품 / 과자류</h1>

    		<app-child
    			*ngFor="let item of list"
    			[item]="item"
    			(pushItem)="pushItem($event)">
    		</app-child>

    	<h1>내 장바구니</h1>
    	<div *ngFor="let item of cartList">
    	  {{item}}: {{cart[item]}}개
    	</div>
    	`
    })
    export class ParentComponent {
      cart={};
      cartList = [];
      list=['초코파이', '빅파이', '참크래커', '마이쮸', '자이리톨', '꼬깔콘']
      pushItem (item) {
        this.cart[item]
        ? this.cart[item]++
        : (this.cart[item] = 1, this.cartList.push(item))
      }
    }

(+) 참고

🚀 입력 프로퍼티를 세터(setter)로 가로채기

부모 컴포넌트에서 값이 전달될 때 추가 로직을 실행하기 위해 입력 프로퍼티에 세터를 사용할 수 있습니다.

child

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

    @Component({
      selector: 'app-name-child',
      template: '<h3>"{{name}}"</h3>'
    })
    export class NameChildComponent {
      @Input()
      get name(): string { return this._name; }
      set name(name: string) {
        this._name = (name && name.trim()) || '<no name set>';
      }
      private _name = '';
    }

parent

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

    @Component({
      selector: 'app-name-parent',
      template: `
      <h2>Master controls {{names.length}} names</h2>
      <app-name-child *ngFor="let name of names" [name]="name"></app-name-child>
      `
    })
    export class NameParentComponent {
      // 'Dr IQ', '<빈 값>', 'Bombasto'를 표시합니다.
      names = ['Dr IQ', '   ', '  Bombasto  '];
    }

🚀 ngOnChanges()로 입력 프로퍼티 가로채기

입력 프로퍼티는 `[OnChanges](https://angular.kr/api/core/OnChanges)` 라이프싸이클 후킹 인터페이스를 사용하는 `ngOnChanges()` 메소드로도 가로챌 수 있습니다.

입력 프로퍼티 여러개를 가로채야 한다면 세터를 사용하는 것보다 이 방식이 더 편할 수 있습니다.

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

    @Component({
      selector: 'app-version-child',
      template: `
        <h3>Version {{major}}.{{minor}}</h3>
        <h4>Change log:</h4>
        <ul>
          <li *ngFor="let change of changeLog">{{change}}</li>
        </ul>
      `
    })
    export class VersionChildComponent implements OnChanges {
      @Input() major: number;
      @Input() minor: number;
      changeLog: string[] = [];

      ngOnChanges(changes: SimpleChanges) {
        const log: string[] = [];
        for (const propName in changes) {
          const changedProp = changes[propName];
          const to = JSON.stringify(changedProp.currentValue);
          if (changedProp.isFirstChange()) {
            log.push(`Initial value of ${propName} set to ${to}`);
          } else {
            const from = JSON.stringify(changedProp.previousValue);
            log.push(`${propName} changed from ${from} to ${to}`);
          }
        }
        this.changeLog.push(log.join(', '));
      }
    }

child는 major와 minor 두 입력 프로퍼티 값이 변경되는 것을 감지하고 이 내용을 로그로 출력합니다:

parent

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

    @Component({
      selector: 'app-version-parent',
      template: `
        <h2>Source code version</h2>
        <button (click)="newMinor()">New minor version</button>
        <button (click)="newMajor()">New major version</button>
        <app-version-child [major]="major" [minor]="minor"></app-version-child>
      `
    })
    export class VersionParentComponent {
      major = 1;
      minor = 23;

      newMinor() {
        this.minor++;
      }

      newMajor() {
        this.major++;
        this.minor = 0;
      }
    }

🚀 템플릿 지역변수로 자식 컴포넌트 접근하기 (부모 템플릿에서 자식 컴포넌트 접근)

부모 컴포넌트는 자식 컴포넌트의 프로퍼티나 메소드에 직접 접근할 수 없습니다.
하지만 부모 템플릿에 템플릿 참조 변수를 선언하면,
자식 컴포넌트의 프로퍼티나 메소드에 접근할 수 있습니다.

child

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

    @Component({
      selector: 'app-countdown-timer',
      template: '<p>{{message}}</p>'
    })
    export class CountdownTimerComponent implements OnDestroy {

      intervalId = 0;
      message = '';
      seconds = 11;

      ngOnDestroy() { this.clearTimer(); }

      start() { this.countDown(); }
      stop()  {
        this.clearTimer();
        this.message = `Holding at T-${this.seconds} seconds`;
      }

      private clearTimer() { clearInterval(this.intervalId); }

      private countDown() {
        this.clearTimer();
        this.intervalId = window.setInterval(() => {
          this.seconds -= 1;
          if (this.seconds === 0) {
            this.message = 'Blast off!';
          } else {
            if (this.seconds < 0) { this.seconds = 10; } // reset
            this.message = `T-${this.seconds} seconds and counting`;
          }
        }, 1000);
      }
    }

parent

    import { Component } from '@angular/core';
    import { CountdownTimerComponent } from './countdown-timer.component';

    @Component({
      selector: 'app-countdown-parent-lv',
      template: `
    	  <h3>Countdown to Liftoff (via local variable)</h3>
    	  <button (click)="timer.start()">Start</button>
    	  <button (click)="timer.stop()">Stop</button>
    	  <div class="seconds">{{timer.seconds}}</div>
    	  <app-countdown-timer #timer></app-countdown-timer>
      `,
      styleUrls: ['../assets/demo.css']
    })
    export class CountdownLocalVarParentComponent { }

👉
원래 부모 컴포넌트는 자식 컴포넌트의 seconds 프로퍼티나 startstop 메소드에 직접 접근할 수 없습니다.
하지만 <countdown-timer>를 템플릿 지역 변수 #timer로 선언하면 이 변수를 사용해서 자식 컴포넌트에 접근할 수 있습니다.

이 템플릿 지역 변수는 자식 컴포넌트 자체를 가리키며, 템플릿 지역 변수를 선언한 후에는 부모 컴포넌트의 템플릿에서 자식 컴포넌트의 프로퍼티나 메소드에 자유롭게 접근할 수 있습니다.


🚀 ViewChild로 자식 컴포넌트 접근하기 (부모 클래스에서 자식 컴포넌트 접근)

템플릿 지역 변수는 부모 컴포넌트의 템플릿에서만 자식 컴포넌트에 접근할 수 있기 때문에 자유롭게 활용하기에는 제한이 있습니다. 부모 컴포넌트의 클래스에서는 자식 컴포넌트에 접근할 수 없기 때문입니다.

부모 컴포넌트의 클래스에서 자식 컴포넌트에 접근하려면 자식 컴포넌트에 ViewChild를 사용해서 부모 컴포넌트로 주입(inject)해야 합니다.

child

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

    @Component({
      selector: 'app-countdown-timer',
      template: '<p>{{message}}</p>'
    })
    export class CountdownTimerComponent implements OnDestroy {

      intervalId = 0;
      message = '';
      seconds = 11;

      ngOnDestroy() { this.clearTimer(); }

      start() { this.countDown(); }
      stop()  {
        this.clearTimer();
        this.message = `Holding at T-${this.seconds} seconds`;
      }

      private clearTimer() { clearInterval(this.intervalId); }

      private countDown() {
        this.clearTimer();
        this.intervalId = window.setInterval(() => {
          this.seconds -= 1;
          if (this.seconds === 0) {
            this.message = 'Blast off!';
          } else {
            if (this.seconds < 0) { this.seconds = 10; } // reset
            this.message = `T-${this.seconds} seconds and counting`;
          }
        }, 1000);
      }
    }

parent

    import { AfterViewInit, ViewChild } from '@angular/core';
    import { Component } from '@angular/core';
    import { CountdownTimerComponent } from './countdown-timer.component';

    @Component({
      selector: 'app-countdown-parent-vc',
      template: `
      <h3>Countdown to Liftoff (via ViewChild)</h3>
      <button (click)="start()">Start</button>
      <button (click)="stop()">Stop</button>
      <div class="seconds">{{ seconds() }}</div>
      <app-countdown-timer></app-countdown-timer>
      `,
      styleUrls: ['../assets/demo.css']
    })
    export class CountdownViewChildParentComponent implements AfterViewInit {

      @ViewChild(CountdownTimerComponent)
      private timerComponent: CountdownTimerComponent;

      seconds() { return 0; }

      ngAfterViewInit() {
        // `seconds()` 메소드는 `CountdownTimerComponent.seconds`에서 다시 구현합니다.
        // 이 때 개발 모드에서 출력하는 단방향 바인딩 검사 에러를 방지하기 위해
        // 한 싸이클 기다려야 합니다.
        setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
      }

      start() { this.timerComponent.start(); }
      stop() { this.timerComponent.stop(); }
    }

👉
먼저, ViewChild 데코레이터와 AfterViewInit 라이프싸이클 후킹 인터페이스를 로드합니다.

그리고 CountdownTimerComponent를 timerComponent 프로퍼티로 선언하면서 @ViewChild 데코레이터를 사용했습니다.

템플릿 지역변수가 자식 컴포넌트의 메소드를 직접 실행했던 것과는 달리,
자식 컴포넌트를 직접 호출하지 않고 부모 컴포넌트에 있는 startstop 메소드를 사용하며, 현재 남아있는 초를 확인할 때도 부모 컴포넌트의 seconds 메소드를 활용합니다. 각각의 메소드에서 자식 컴포넌트에 접근하는 식으로 구현하는 것입니다.

⚠️ 라이프 사이클 고려하기

이 때 ngAfterViewInit() 라이프싸이클 후킹 함수가 중요합니다.

자식 컴포넌트인 타이머 컴포넌트는 Angular가 부모 컴포넌트의 뷰를 화면에 표시한 이후에야 사용할 수 있습니다. 그래서 뷰가 완전히 준비되기 전까지는 0을 표시합니다.

부모 컴포넌트의 뷰가 준비되면 자식 컴포넌트에서 시간을 가져오기 위해 ngAfterViewInit 라이프싸이클 후킹 함수를 실행하는데, Angular는 단방향 데이터 흐름을 권장하기 때문에 부모 컴포넌트의 뷰를 같은 JavaScript 실행 싸이클 안에서 갱신하는 것을 금지합니다.

그래서 ngAfterViewInit()에서 자식 컴포넌트의 시간을 가져와서 부모 컴포넌트 프로퍼티에 할당하는 것은 setTimeout() 으로 한 싸이클 늦췄습니다.


4) Service를 이용한 통신

부모 컴포넌트와 자식 컴포넌트가 같은 서비스를 주입받는다면
이 서비스를 활용해서 양방향으로 데이터를 주고받을 수 있습니다.

컴포넌트에 주입되는 서비스는 그 컴포넌트에서 자유롭게 사용할 수 있습니다.
⚠️ 이 때 주입되는 서비스의 인스턴스가 동일해야 하기 때문에,
서비스 프로바이더를 별도로 지정하면 컴포넌트 통신에 활용할 수 없습니다.

service

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

@Injectable()
export class MissionService {

  // 문자열 타입의 옵저버블 소스
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();

  // 문자열 옵저버블 스트림
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();

  // 서비스가 옵저버블을 전달할 때 사용하는 메소드
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }

  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}

MissionControl (Parent)

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

import { MissionService } from './mission.service';

@Component({
  selector: 'app-mission-control',
  template: `
    <h2>Mission Control</h2>
    <button (click)="announce()">Announce mission</button>
    <app-astronaut *ngFor="let astronaut of astronauts"
      [astronaut]="astronaut">
    </app-astronaut>
    <h3>History</h3>
    <ul>
      <li *ngFor="let event of history">{{event}}</li>
    </ul>
  `,
  providers: [MissionService]
})
export class MissionControlComponent {
  astronauts = ['Lovell', 'Swigert', 'Haise'];
  history: string[] = [];
  missions = ['Fly to the moon!',
              'Fly to mars!',
              'Fly to Vegas!'];
  nextMission = 0;

  constructor(private missionService: MissionService) {
    missionService.missionConfirmed$.subscribe(
      astronaut => {
        this.history.push(`${astronaut} confirmed the mission`);
      });
  }

  announce() {
    const mission = this.missions[this.nextMission++];
    this.missionService.announceMission(mission);
    this.history.push(`Mission "${mission}" announced`);
    if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
  }
}

MissionControlComponent는 생성자를 통해 MissionService의 인스턴스를 주입받으며,
providers 메타데이터를 사용해서 서비스 인스턴스를 자식 컴포넌트에서도 사용할 수 있도록 공유합니다:

Astronaut (Child)

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

import { MissionService } from './mission.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}

그리고 자식 컴포넌트 AstronautComponent도 생성자를 통해 서비스 인스턴스를 주입 받습니다:


2. 서로 다른 트리에 존재할 때

1) Observable, 서비스를 사용한 컴포넌트간의 통신

앵귤러에서는 Subject 객체를 생성함으로써 Observable 변수를 만들 수 있습니다.

Observable 변수는 해당 변수의 변화를 구독자에게 전달할 수 있는 기능을 가지고 있는 변수를 말합니다.
즉, Global하게 접근할 수 있는 Observable 변수를 만들어 놓고, 해당 변수의 변화되는 값을 사용하고자 하는 컴포넌트에서 Observable 변수를 구독하여 사용하면 됩니다.
(출처 : https://secjong.tistory.com/8)

매개자 역할의 서비스 생성

import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
 
@Injectable()
export class DataService {
  private subject = new Subject<any>();
 
  constructor() { }
 
  sendData(data){
    this.subject.next(data);
    console.log("sendData() data : " , data);
  }
 
  getData(){
    return this.subject.asObservable();
  }
}

👉
먼저, Observable 변수를 선언하기 위한 서비스를 생성합니다.
rxjs 의 Subject 객체는 Observable 인 동시에 Observer입니다.

한 컴포넌트에서 서비스의 sendData() 를 호출하여
데이터를 Subject 의 next 메서드를 통해서 데이터 스트림에 밀어넣습니다.
다른 getData() 메서드는 데이터를 받을 컴포넌트에서 호출하여 데이터스트림에서 Observable 객체를 받은 후, 데이터 전송이 완료되었을 때 구독(subscribe) 할 것입니다.

데이터를 보낼 컴포넌트 (sendData(data))

import { Component, OnInit, OnDestroy } from '@angular/core';
import { PostService } from '../../services/post/post.service';
import { DataService } from '../../services/data.service';
 
import { Post } from '../../models/post';
import { ActivatedRoute, Params } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
 
@Component({
  selector: 'app-post',
  templateUrl: './post.component.html',
  styleUrls: ['./post.component.css']
})
export class PostComponent implements OnInit, OnDestroy {
  private subscription: Subscription;
  post: Post;
  postNo: number;
 
  constructor(private activatedRouter: ActivatedRoute,
					private postService: PostService,
					private dataService: DataService) {
    this.subscription = activatedRouter.params.subscribe((params: Params) => {
      this.postNo = params['postNo'];
      this.postService.getPost(this.postNo)
      .subscribe(
        (post) => {
          this.post = post;
          dataService.sendData(this.post);
        },
        (error) => {
          console.log(error);
        }
      );
    });
  }
 
  ngOnInit() {
    
  }
 
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

핵심은 위에서 생성한 서비스의 sendData() 를 호출하는 것입니다.
DataService 를 주입받고, 보내고자 하는 데이터를 dataService.sendData() 로 호출하여 넘겨줍니다.
Subscription 객체는 구독했던 객체를 파괴하기 위해 사용합니다.
(ngOnDestroy() 라이프사이클에서, 저장했던 subscription 을 unsubscribe)

데이터를 받을 컴포넌트 (getData())

import { Component, OnInit, OnDestroy } from '@angular/core';
import { DataService } from '../../services/data.service';
 
import { Subscription } from 'rxjs/Subscription';
 
@Component({
  selector: 'app-banner',
  templateUrl: './banner.component.html',
  styleUrls: ['./banner.component.css']
})
export class BannerComponent implements OnInit, OnDestroy {
  title = 'Blog';
  regDate = '';
  categoryName = '';
 
  subscription: Subscription;
 
  constructor(private dataService: DataService) {
    console.log("banner 컴포넌트 생성!");
    
    this.subscription = dataService.getData().subscribe(data => {
      console.log("banner subscription : " , data);
      this.title = data.title;
      this.regDate = data.regTime;
      this.categoryName = data.categoryId;
    })
 
  }
 
  ngOnInit() {
  }
 
  ngOnDestroy(){
    this.subscription.unsubscribe();
  }
}

데이터를 받을 컴포넌트에서는 DataService 를 주입받고,
이 DataService 의 getData() 메서드를 호출하여 Observable 객체를 받습니다.
이를 subscribe 메서드를 사용하여 데이터가 전달되면 수행할 작업을 진행하면 됩니다.
데이터를 수신할 컴포넌트에서도 마찬가지로 Subscription 객체를 이용해 구독한 객체를 파괴시켜주어야 합니다.

좋은 웹페이지 즐겨찾기