Angular 2
Component interacties
Zoeken…
Invoering
Deel informatie tussen verschillende richtlijnen en componenten.
Gegevens doorgeven van ouder op kind met invoerbinding
HeroChildComponent heeft twee invoereigenschappen, meestal versierd met @Input-decoraties.
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;
}
Intercept-eigenschappen veranderen met een setter
Gebruik een invoereigenschapsinsteller om een waarde van de ouder te onderscheppen en er naar te handelen.
De setter van de eigenschap name input in het onderliggende NameChildComponent snijdt de witruimte van een naam en vervangt een lege waarde door standaardtekst.
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; }
}
Hier is de NameParentComponent die naamvariaties toont, inclusief een naam met alle spaties:
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 '];
}
Ouder luistert naar kindergebeurtenis
De onderliggende component onthult een eigenschap EventEmitter waarmee het gebeurtenissen uitzendt wanneer er iets gebeurt. De ouder bindt zich aan die gebeurteniseigenschap en reageert op die gebeurtenissen.
De eigenschap EventEmitter van het kind is een eigenschap output, meestal versierd met een decoratie @Output zoals te zien in deze 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;
}
}
Als u op een knop klikt, wordt de emissie van een waar of onwaar (de booleaanse payload) geactiveerd.
De bovenliggende VoteTakerComponent bindt een gebeurtenishandler (onVended) die reageert op de payload van de onderliggende gebeurtenis ($ event) en een teller bijwerkt.
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++;
}
}
Ouder heeft interactie met kind via lokale variabele
Een bovenliggend onderdeel kan geen gegevensbinding gebruiken om onderliggende eigenschappen te lezen of onderliggende methoden aan te roepen. We kunnen beide doen door een sjabloonreferentievariabele voor het onderliggende element te maken en vervolgens naar die variabele in de bovenliggende sjabloon te verwijzen, zoals te zien in het volgende voorbeeld.
We hebben een kind CountdownTimerComponent dat herhaaldelijk aftelt naar nul en een raket lanceert. Het heeft start- en stopmethoden die de klok besturen en toont een countdown-statusbericht in zijn eigen sjabloon.
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);
}
}
Laten we eens kijken naar de CountdownLocalVarParentComponent die de timercomponent host.
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 { }
De bovenliggende component kan geen gegevens binden aan de start- en stopmethoden van het kind, noch aan de eigenschap seconds.
We kunnen een lokale variabele (#timer) op de tag () plaatsen die de onderliggende component vertegenwoordigt. Dat geeft ons een verwijzing naar de onderliggende component zelf en de mogelijkheid om toegang te krijgen tot een van de eigenschappen of methoden vanuit de bovenliggende sjabloon.
In dit voorbeeld koppelen we bovenliggende knoppen aan het begin en einde van het kind en gebruiken we interpolatie om de eigenschap seconden van het kind weer te geven.
Hier zien we de ouder en het kind samenwerken.
Ouder roept een ViewChild op
De lokale variabele benadering is eenvoudig en gemakkelijk. Maar het is beperkt omdat de bedrading tussen ouder en kind volledig binnen de bovenliggende sjabloon moet worden uitgevoerd. De bovenliggende component zelf heeft geen toegang tot het kind.
We kunnen de techniek van de lokale variabele niet gebruiken als een instantie van de bovenliggende componentklasse waarden van onderliggende componenten moet lezen of schrijven of methoden van onderliggende componenten moet aanroepen.
Wanneer de bovenliggende componentklasse dat soort toegang vereist, injecteren we de onderliggende component in de ouder als ViewChild.
We illustreren deze techniek met hetzelfde Countdown Timer-voorbeeld. We zullen het uiterlijk of gedrag niet veranderen. Het onderliggende CountdownTimerComponent is ook hetzelfde.
We schakelen alleen van de lokale variabele naar de ViewChild-techniek voor demonstratiedoeleinden. Hier is de ouder, 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(); }
}
Het kost wat meer werk om de onderliggende weergave in de bovenliggende componentklasse te krijgen.
We importeren verwijzingen naar de ViewChild-decorateur en de AfterViewInit-levenscyclushaak.
We injecteren het onderliggende CountdownTimerComponent in de eigenschap private timerComponent via de eigenschapdecoratie @ViewChild.
De lokale variabele #timer is verdwenen uit de metagegevens van de component. In plaats daarvan binden we de knoppen aan de eigen start- en stopmethoden van de bovenliggende component en presenteren we de tikkende seconden in een interpolatie rond de secondenmethode van de bovenliggende component.
Deze methoden hebben rechtstreeks toegang tot de geïnjecteerde timercomponent.
De levenscyclushaak ngAfterViewInit is een belangrijke rimpel. De timercomponent is pas beschikbaar nadat Angular het bovenaanzicht toont. Dus we geven aanvankelijk 0 seconden weer.
Vervolgens roept Angular de levenscyclushaak ngAfterViewInit op waarop het te laat is om de weergave van de aftellende seconden in de bovenliggende weergave bij te werken. De unidirectionele datastroomregel van Angular voorkomt dat we de bovenliggende weergave in dezelfde cyclus bijwerken. We moeten één beurt wachten voordat we de seconden kunnen weergeven.
We gebruiken setTimeout om één vinkje te wachten en vervolgens de secondenmethode te herzien zodat toekomstige waarden uit de timercomponent worden overgenomen.
Ouder en kinderen communiceren via een dienst
Een oudercomponent en zijn kinderen delen een service waarvan de interface bidirectionele communicatie binnen het gezin mogelijk maakt.
Het bereik van de service-instantie is de bovenliggende component en de onderliggende componenten. Componenten buiten deze substructuur hebben geen toegang tot de service of hun communicatie.
Deze MissionService verbindt de MissionControlComponent met meerdere AstronautComponent-kinderen.
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);
}
}
De MissionControlComponent biedt beide het exemplaar van de service dat het deelt met zijn kinderen (via de metagegevensmatrix van de provider) en injecteert dat exemplaar in zichzelf via de constructor:
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; }
}
}
De AstronautComponent injecteert de service ook in de constructor. Elke AstronautComponent is een kind van de MissionControlComponent en ontvangt daarom het service-exemplaar van de ouder:
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();
}
}
Merk op dat we het abonnement vastleggen en het abonnement opzeggen wanneer de AstronautComponent wordt vernietigd. Dit is een geheugenlekbewakingsstap. Er is geen reëel risico in deze app omdat de levensduur van een Astronaut-component hetzelfde is als de levensduur van de app zelf. Dat zou niet altijd het geval zijn in een meer complexe toepassing.
We voegen deze bewaker niet toe aan de MissionControlComponent omdat deze als ouder de levensduur van de MissionService beheert. Het logboek Geschiedenis laat zien dat berichten in beide richtingen reizen tussen de bovenliggende MissionControlComponent en de AstronautComponent-kinderen, gefaciliteerd door de service: