Angular 2
구성 요소 상호 작용
수색…
소개
서로 다른 지시어와 구성 요소간에 정보를 공유하십시오.
입력 바인딩을 사용하여 부모에서 자식으로 데이터 전달
HeroChildComponent에는 일반적으로 @Input 장식으로 장식 된 두 개의 입력 속성이 있습니다.
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: '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;
}
setter를 사용하여 입력 속성 변경 차단
입력 속성 setter를 사용하여 부모의 값을 가로 채고 작업합니다.
NameChildComponent 자식 이름 입력 등록 정보의 설정자는 이름에서 공백을 잘라내어 빈 값을 기본 텍스트로 바꿉니다.
import { Component, Input } from '@angular/core';
@Component({
selector: 'name-child',
template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
private _name = '';
@Input()
set name(name: string) {
this._name = (name && name.trim()) || '<no name set>';
}
get name(): string { return this._name; }
}
다음은 모든 공백이있는 이름을 포함하여 이름 변형을 보여주는 NameParentComponent입니다.
import { Component } from '@angular/core';
@Component({
selector: 'name-parent',
template: `
<h2>Master controls {{names.length}} names</h2>
<name-child *ngFor="let name of names" [name]="name"></name-child>
`
})
export class NameParentComponent {
// Displays 'Mr. IQ', '<no name set>', 'Bombasto'
names = ['Mr. IQ', ' ', ' Bombasto '];
}
부모는 자식 이벤트를 청취합니다.
자식 구성 요소는 이벤트가 발생할 때 이벤트를 내보내는 EventEmitter 속성을 제공합니다. 부모는 해당 이벤트 속성에 바인딩하고 해당 이벤트에 반응합니다.
자식의 EventEmitter 속성은 출력 속성입니다. 일반적으로이 VoterComponent에 표시된 @Output 데코레이션으로 장식되어 있습니다.
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'my-voter',
template: `
<h4>{{name}}</h4>
<button (click)="vote(true)" [disabled]="voted">Agree</button>
<button (click)="vote(false)" [disabled]="voted">Disagree</button>
`
})
export class VoterComponent {
@Input() name: string;
@Output() onVoted = new EventEmitter<boolean>();
voted = false;
vote(agreed: boolean) {
this.onVoted.emit(agreed);
this.voted = true;
}
}
버튼을 클릭하면 true 또는 false (부울 페이로드)가 방출됩니다.
부모 VoteTakerComponent는 자식 이벤트 페이로드 ($ event)에 응답하고 카운터를 업데이트하는 이벤트 처리기 (onVoted)를 바인딩합니다.
import { Component } from '@angular/core';
@Component({
selector: 'vote-taker',
template: `
<h2>Should mankind colonize the Universe?</h2>
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
<my-voter *ngFor="let voter of voters"
[name]="voter"
(onVoted)="onVoted($event)">
</my-voter>
`
})
export class VoteTakerComponent {
agreed = 0;
disagreed = 0;
voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];
onVoted(agreed: boolean) {
agreed ? this.agreed++ : this.disagreed++;
}
}
부모는 지역 변수를 통해 자식과 상호 작용합니다.
부모 구성 요소는 데이터 바인딩을 사용하여 하위 속성을 읽거나 하위 메서드를 호출 할 수 없습니다. 두 요소 모두에 대해 템플릿 요소 참조를 만들고 다음 예제와 같이 상위 템플릿 내에서 해당 변수를 참조 할 수 있습니다.
반복적으로 0으로 카운트 다운하고 로켓을 발사하는 자식 CountdownTimerComponent가 있습니다. 시계를 제어하는 시작 및 중지 메소드가 있으며 자체 템플릿에 카운트 다운 상태 메시지가 표시됩니다.
import { Component, OnDestroy, OnInit } from '@angular/core';
@Component({
selector: 'countdown-timer',
template: '<p>{{message}}</p>'
})
export class CountdownTimerComponent implements OnInit, OnDestroy {
intervalId = 0;
message = '';
seconds = 11;
clearTimer() { clearInterval(this.intervalId); }
ngOnInit() { this.start(); }
ngOnDestroy() { this.clearTimer(); }
start() { this.countDown(); }
stop() {
this.clearTimer();
this.message = `Holding at T-${this.seconds} seconds`;
}
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);
}
}
타이머 구성 요소를 호스팅하는 CountdownLocalVarParentComponent를 보겠습니다.
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';
@Component({
selector: '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>
<countdown-timer #timer></countdown-timer>
`,
styleUrls: ['demo.css']
})
export class CountdownLocalVarParentComponent { }
부모 구성 요소는 자식의 시작 및 중지 메서드 나 초 속성에 데이터를 바인딩 할 수 없습니다.
자식 구성 요소를 나타내는 태그 ()에 지역 변수 (#timer)를 배치 할 수 있습니다. 이는 하위 구성 요소 자체에 대한 참조와 부모 템플릿 내에서 해당 속성이나 메서드에 액세스 할 수있는 기능을 제공합니다.
이 예제에서는 부모 버튼을 자식의 start에 연결하고 보간을 사용하여 자식의 seconds 속성을 표시합니다.
여기서 우리는 부모와 자식이 함께 작업하는 것을 봅니다.
부모가 ViewChild를 호출합니다.
지역 변수 접근법은 간단하고 쉽습니다. 그러나 부모 - 자식 배선은 상위 템플릿 내에서 완전히 이루어져야하기 때문에 제한적입니다. 부모 컴포넌트 자체는 자식에 접근 할 수 없다.
부모 컴포넌트 클래스의 인스턴스가 자식 컴포넌트 값을 읽거나 쓰거나 자식 컴포넌트 메소드를 호출해야하는 경우 로컬 변수 기술을 사용할 수 없습니다.
부모 컴포넌트 클래스가 그러한 종류의 액세스를 필요로 할 때, 우리는 자식 컴포넌트를 부모 클래스에 ViewChild로 삽입한다.
이 기술을 동일한 카운트 다운 타이머 예제로 설명 할 것입니다. 우리는 외모 나 행동을 바꾸지 않을 것입니다. 자식 CountdownTimerComponent도 동일합니다.
우리는 데모 목적으로 만 로컬 변수에서 ViewChild 기술로 전환하고 있습니다. 다음은 부모 인 CountdownViewChildParentComponent입니다.
import { AfterViewInit, ViewChild } from '@angular/core';
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';
@Component({
selector: '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>
<countdown-timer></countdown-timer>
`,
styleUrls: ['demo.css']
})
export class CountdownViewChildParentComponent implements AfterViewInit {
@ViewChild(CountdownTimerComponent)
private timerComponent: CountdownTimerComponent;
seconds() { return 0; }
ngAfterViewInit() {
// Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
// but wait a tick first to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
}
start() { this.timerComponent.start(); }
stop() { this.timerComponent.stop(); }
}
하위 뷰를 상위 구성 요소 클래스로 가져 오려면 좀 더 많은 작업이 필요합니다.
ViewChild 데코레이터 및 AfterViewInit 라이프 사이클 후크에 대한 참조를 가져옵니다.
우리는 @ViewChild 속성 데코레이션을 통해 자식 timerInputComponent를 개인 timerComponent 속성에 삽입합니다.
#timer 로컬 변수가 구성 요소 메타 데이터에서 사라졌습니다. 대신 버튼을 부모 컴포넌트 자체의 시작 및 중지 메소드에 바인딩하고 부모 컴포넌트의 초 메소드 주위에 보간법을 적용하여 초를 표시합니다.
이 메소드는 주입 된 타이머 구성 요소에 직접 액세스합니다.
ngAfterViewInit 라이프 사이클 후크는 중요한 주름입니다. Angular가 부모보기를 표시 할 때까지 타이머 구성 요소를 사용할 수 없습니다. 그러면 처음에는 0 초가 표시됩니다.
그런 다음 Angular가 ngAfterViewInit 라이프 사이클 후크를 호출하면 부모 뷰의 카운트 다운 초 표시를 업데이트하기에는 너무 늦습니다. Angular의 단방향 데이터 흐름 규칙은 동일한주기 내에서 부모보기를 업데이트하지 못하게합니다. 초를 표시하기 전에 한 턴을 기다려야합니다.
우리는 setTimeout을 사용하여 1 tick을 기다린 다음 seconds 메소드를 수정하여 타이머 구성 요소에서 미래 값을 가져옵니다.
부모와 자녀는 서비스를 통해 의사 소통합니다.
부모 구성 요소와 하위 구성 요소는 인터페이스가 해당 패밀리 내에서 양방향 통신을 가능하게하는 서비스를 공유합니다.
서비스 인스턴스의 범위는 상위 구성 요소와 그 하위 요소입니다. 이 구성 요소 하위 트리 외부의 구성 요소는 서비스 또는 통신에 액세스 할 수 없습니다.
이 MissionService는 MissionControlComponent를 여러 AstronautComponent 자식과 연결합니다.
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class MissionService {
// Observable string sources
private missionAnnouncedSource = new Subject<string>();
private missionConfirmedSource = new Subject<string>();
// Observable string streams
missionAnnounced$ = this.missionAnnouncedSource.asObservable();
missionConfirmed$ = this.missionConfirmedSource.asObservable();
// Service message commands
announceMission(mission: string) {
this.missionAnnouncedSource.next(mission);
}
confirmMission(astronaut: string) {
this.missionConfirmedSource.next(astronaut);
}
}
MissionControlComponent는 공급자 메타 데이터 배열을 통해 자식과 공유하는 서비스의 인스턴스를 제공하고 생성자를 통해 인스턴스를 자체에 주입합니다.
import { Component } from '@angular/core';
import { MissionService } from './mission.service';
@Component({
selector: 'mission-control',
template: `
<h2>Mission Control</h2>
<button (click)="announce()">Announce mission</button>
<my-astronaut *ngFor="let astronaut of astronauts"
[astronaut]="astronaut">
</my-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() {
let mission = this.missions[this.nextMission++];
this.missionService.announceMission(mission);
this.history.push(`Mission "${mission}" announced`);
if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
}
}
AstronautComponent는 또한 생성자에 서비스를 삽입합니다. 각 AstronautComponent는 MissionControlComponent의 자식이므로 부모의 서비스 인스턴스를받습니다.
import { Component, Input, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'my-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가 파괴되면 구독을 취소하고 구독을 취소합니다. 이것은 메모리 누출 가드 단계입니다. AstronautComponent의 수명이 앱 자체의 수명과 동일하기 때문에이 앱에는 실제 위험이 없습니다. 더 복잡한 응용 프로그램에서 항상 사실 일 수는 없습니다.
부모로서 MissionService의 수명을 제어하기 때문에이 가드를 MissionControlComponent에 추가하지 않습니다. 히스토리 로그는 메시지가 상위 MissionControlComponent와 AstronautComponent 하위 사이에서 양방향으로 이동 함을 보여줍니다.