Angular 2
Komponentinteraktioner
Sök…
Introduktion
Dela information mellan olika direktiv och komponenter.
Skicka data från förälder till barn med ingående bindning
HeroChildComponent har två inmatningsegenskaper, vanligtvis prydda med @Input-dekorationer.
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;
}
Avlyssna ingångsegenskapen ändras med en setter
Använd en inmatningsegenskapssättare för att fånga upp och agera efter ett värde från föräldern.
Sättaren för inmatningsegenskapen för namnet i barnet NameChildComponent klipper vitrummet från ett namn och ersätter ett tomt värde med standardtext.
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; }
}
Här är NameParentComponent som visar namnvariationer inklusive ett namn med alla mellanslag:
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 '];
}
Förälder lyssnar på barnevenemang
Barnkomponenten exponerar en EventEmitter-egenskap som den avger händelser när något händer. Föräldern binder till den händelseegenskapen och reagerar på dessa händelser.
Barnets EventEmitter-egenskap är en utgångsegenskap, vanligtvis prydd med en @Output-dekoration som det ses i denna VoterComponent:
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;
}
}
Om du klickar på en knapp utlöses en sann eller falsk utsläpp (den booleska nyttolasten).
Föräldern VoteTakerComponent binder en händelseshanterare (onVoting) som svarar på barndomens nyttolast ($ event) och uppdaterar en räknare.
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++;
}
}
Förälder interagerar med barn via lokal variabel
En överordnad komponent kan inte använda databindande för att läsa barns egenskaper eller åberopa barnmetoder. Vi kan göra båda genom att skapa en mallreferensvariabel för underordnadselementet och sedan referera till den variabeln i överordnad mall som ses i följande exempel.
Vi har en barn CountdownTimerComponent som upprepade gånger räknas ner till noll och lanserar en raket. Det har start- och stoppmetoder som styr klockan och det visar ett nedräkningsstatusmeddelande i sin egen mall.
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);
}
}
Låt oss se CountdownLocalVarParentComponent som är värd för timerkomponenten.
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 { }
Den överordnade komponenten kan inte binda till barnets start- och stoppmetoder eller till dess sekunders egenskap.
Vi kan placera en lokal variabel (#timer) på taggen () som representerar barnkomponenten. Det ger oss en hänvisning till själva barnkomponenten och förmågan att komma åt någon av dess egenskaper eller metoder från föräldermallen.
I det här exemplet kopplar vi överknappar till barnets start och stopp och använder interpolering för att visa barnets sekunderegenskap.
Här ser vi föräldern och barnet arbeta tillsammans.
Förälder ringer ett ViewChild
Den lokala variabla strategin är enkel och enkel. Men det är begränsat eftersom kablarna mellan föräldrar och barn måste göras helt inom föräldermallen. Föräldrakomponenten själv har ingen tillgång till barnet.
Vi kan inte använda den lokala variabla tekniken om en instans av klassen för överordnad komponent måste läsa eller skriva underkomponentvärden eller måste ringa barnkomponentmetoder.
När klassen för föräldrakomponent kräver den typen av åtkomst, injicerar vi barnkomponenten i föräldern som en ViewChild.
Vi illustrerar den här tekniken med samma Countdown Timer-exempel. Vi kommer inte att ändra dess utseende eller beteende. Barnet CountdownTimerComponent är också detsamma.
Vi byter från den lokala variabeln till ViewChild-tekniken enbart för att demonstrera. Här är föräldern, 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(); }
}
Det krävs lite mer arbete för att få barnvisningen till klassen för föräldrakomponenter.
Vi importerar referenser till ViewChild-dekoratören och AfterViewInit-livscykelkroken.
Vi injicerar Child CountdownTimerComponent i den privata timerComponent-egenskapen via egenskapen @ViewChild.
Den lokala variabeln #timer försvinner från komponentmetadata. Istället binder vi knapparna till moderkomponentens egna start- och stoppmetoder och presenterar kryssningssekunderna i en interpolering kring moderkomponentens sekundmetod.
Dessa metoder får direkt tillgång till den injicerade timerkomponenten.
Livscykelkroken ngAfterViewInit är en viktig rynka. Timerkomponenten är inte tillgänglig förrän när Angular visar föräldervyn. Så vi visar 0 sekunder från början.
Sedan ringer Angular livscykelkroken ngAfterViewInit vid vilken tidpunkt det är för sent att uppdatera föräldrarnas visning av nedräkningssekunderna. Angulars enkelriktade dataflödesregel förhindrar oss från att uppdatera överordnade vyn i samma cykel. Vi måste vänta en tur innan vi kan visa sekunderna.
Vi använder setTimeout för att vänta en kryss och sedan revidera sekundmetoden så att den tar framtida värden från timerkomponenten.
Förälder och barn kommunicerar via en tjänst
En överordnad komponent och dess barn delar en tjänst vars gränssnitt möjliggör dubbelriktad kommunikation i familjen.
Omfattningen av tjänsteinstansen är föräldrakomponenten och dess barn. Komponenter utanför denna komponentundertree har ingen åtkomst till tjänsten eller deras kommunikation.
Denna MissionService ansluter MissionControlComponent till flera AstronautComponent-barn.
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 tillhandahåller både förekomsten av den tjänst som den delar med sina barn (genom leverantörens metadataformat) och injicerar instansen i sig själv genom sin konstruktör:
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 injicerar också tjänsten i sin konstruktör. Varje AstronautComponent är ett barn till MissionControlComponent och får därför sin förälders serviceinstans:
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();
}
}
Lägg märke till att vi fångar prenumerationen och avslutar prenumerationen när AstronautComponent förstörs. Detta är ett minnesläckskyddssteg. Det finns ingen faktisk risk i denna app eftersom en astronautkomponenters livslängd är densamma som själva appens livslängd. Det skulle inte alltid vara sant i en mer komplex applikation.
Vi lägger inte till denna skydd till MissionControlComponent eftersom den som förälder kontrollerar MissionServices livstid. Historikloggen visar att meddelanden reser i båda riktningarna mellan föräldern MissionControlComponent och AstronautComponent-barnen, underlättas av tjänsten: