Używam Angulara 2.0.0-rc.4 z RxJS 5.0.0-beta.6.Jak utworzyć strumień obserwowalny RxJS z elementu potomnego szablonu elementu Angular2
Eksperymentuję z różnymi sposobami tworzenia obserwowalnych strumieni z wydarzeń, ale jestem przytłoczony opcjami i chcę mieć opinię. Doceniam to, że nie ma jednego uniwersalnego rozwiązania i są konie na kursy. Są prawdopodobnie inne techniki, o których nie wiem lub których nie brałem pod uwagę.
Urządzenie udostępnia kilka metod komponentu macierzystego współdziałających ze zdarzeniami komponentu potomnego. Jednak tylko przykład parent and children communicate via a service używa observables i wydaje się, że w większości scenariuszy jest to przesada.
Scenariusz jest taki, że element szablonu emituje dużą liczbę zdarzeń i chcę okresowo sprawdzać, jaka jest ostatnia wartość.
sampleTime
używam metody Observable
jest z okresem 1000ms do monitorowania położenia myszy na elemencie <p>
HTML.
1) Ta technika używa ElementRef
wprowadzonego do konstruktora komponentu w celu uzyskania dostępu do właściwości nativeElement
i zapytania elementów potomnych według nazwy znacznika.
@Component({
selector: 'watch-child-events',
template: `
<p>Move over me!</p>
<div *ngFor="let message of messages">{{message}}</div>
`
})
export class WatchChildEventsComponent implements OnInit {
messages:string[] = [];
constructor(private el:ElementRef) {}
ngOnInit() {
let p = this.el.nativeElement.getElementsByTagName('p')[0];
Observable
.fromEvent(p, 'mousemove')
.sampleTime(1000)
.subscribe((e:MouseEvent) => {
this.messages.push(`${e.type} (${e.x}, ${e.y})`);
});
}
}
opinia Konsensus wydaje się boczyć na tej technice, ponieważ Angular2 zapewnia wystarczającą abstrakcji nad DOM tak, że rzadko, jeśli w ogóle, trzeba kontaktować się bezpośrednio z nim. Jednak ta metoda fabryczna jest bardzo kusząca i była to pierwsza technika, która przyszła jej do głowy.
2) Ta technika używa numeru EventEmitter
, który jest Observable
.
@Component({
selector: 'watch-child-events',
template: `
<p (mousemove)="handle($event)">Move over me!</p>
<div *ngFor="let message of messages">{{message}}</div>
`
})
export class WatchChildEventsComponent implements OnInit {
messages:string[] = [];
emitter:EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
ngOnInit() {
this.emitter
.sampleTime(1000)
.subscribe((e:MouseEvent) => {
this.messages.push(`${e.type} (${e.x}, ${e.y})`);
});
}
handle(e:MouseEvent) {
this.emitter.emit(e);
}
}
Takie rozwiązanie pozwala uniknąć zapytań DOM, ale emiterów zdarzeń są wykorzystywane do komunikowania się z dzieckiem do rodziców, a to zdarzenie nie ma wyjścia.
Przeczytałem here, że nie należy zakładać, że emitery zdarzeń będą widoczne w ostatecznej wersji, więc może to nie być stabilna funkcja, na której można polegać.
3) Ta technika używa możliwego do zaobserwowania Subject
.
@Component({
selector: 'watch-child-events',
template: `
<p (mousemove)="handle($event)">Move over me!</p>
<div *ngFor="let message of messages">{{message}}</div>
`
})
export class WatchChildEventsComponent implements OnInit {
messages:string[] = [];
subject = new Subject<MouseEvent>();
ngOnInit() {
this.subject
.sampleTime(1000)
.subscribe((e:MouseEvent) => {
this.messages.push(`${e.type} (${e.x}, ${e.y})`);
});
}
handle(e:MouseEvent) {
this.subject.next(e);
}
}
To rozwiązanie spełnia wszystkie moje oczekiwania, nie dodając zbytniej złożoności. Mogę użyć ReplaySubject
, aby otrzymać całą historię opublikowanych wartości, gdy ją zasubskrybuję, lub tylko najnowszą, jeśli istnieje, z subject = new ReplaySubject<MouseEvent>(1);
.
4) Ta technika używa szablonu odniesienia w połączeniu z dekoratorem @ViewChild
.
@Component({
selector: 'watch-child-events',
template: `
<p #p">Move over me!</p>
<div *ngFor="let message of messages">{{message}}</div>
`
})
export class WatchChildEventsComponent implements AfterViewInit {
messages:string[] = [];
@ViewChild('p') p:ElementRef;
ngAfterViewInit() {
Observable
.fromEvent(this.p.nativeElement, 'mousemove')
.sampleTime(1000)
.subscribe((e:MouseEvent) => {
this.messages.push(`${e.type} (${e.x}, ${e.y})`);
});
}
}
Podczas pracy pachnie trochę dla mnie. Odniesienia do szablonów służą przede wszystkim do interakcji elementów w szablonie. Dotyka również DOM poprzez nativeElement
, używa łańcuchów do odniesienia do nazwy zdarzenia i odniesienia do szablonu i używa haka cyklu życia AfterViewInit
.
5) Rozszerzyłem ten przykład, aby użyć komponentu niestandardowego, który zarządza Subject
i okresowo emituje zdarzenie.
@Component({
selector: 'child-event-producer',
template: `
<p (mousemove)="handle($event)">
<ng-content></ng-content>
</p>
`
})
export class ChildEventProducerComponent {
@Output() event = new EventEmitter<MouseEvent>();
subject = new Subject<MouseEvent>();
constructor() {
this.subject
.sampleTime(1000)
.subscribe((e:MouseEvent) => {
this.event.emit(e);
});
}
handle(e:MouseEvent) {
this.subject.next(e);
}
}
Jest on używany przez rodzica tak:
@Component({
selector: 'watch-child-events',
template: `
<child-event-producer (event)="handle($event)">
Move over me!
</child-event-producer>
<div *ngFor="let message of messages">{{message}}</div>
`,
directives: [ChildEventProducerComponent]
})
export class WatchChildEventsComponent {
messages:string[] = [];
handle(e:MouseEvent) {
this.messages.push(`${e.type} (${e.x}, ${e.y})`);
}
}
Lubię tę technikę; komponent niestandardowy zawiera pożądane zachowanie i ułatwia rodzicowi korzystanie, ale komunikuje się tylko w drzewie komponentów i nie może powiadomić rodzeństwa.
6) Porównaj to z tą techniką, która po prostu przekazuje wydarzenie od dziecka do rodzica.
@Component({
selector: 'child-event-producer',
template: `
<p (mousemove)="handle($event)">
<ng-content></ng-content>
</p>
`
})
export class ChildEventProducerComponent {
@Output() event = new EventEmitter<MouseEvent>();
handle(e:MouseEvent) {
this.event.emit(e);
}
}
A jest podłączony w dominującej pomocą dekoratora @ViewChild
albo tak:
@Component({
selector: 'watch-child-events',
template: `
<child-event-producer>
Move over me!
</child-event-producer>
<div *ngFor="let message of messages">{{message}}</div>
`,
directives: [ChildEventProducerComponent]
})
export class WatchChildEventsComponent implements AfterViewInit {
messages:string[] = [];
@ViewChild(ChildEventProducerComponent) child:ChildEventProducerComponent;
ngAfterViewInit() {
Observable
.from(this.child.event)
.sampleTime(1000)
.subscribe((e:MouseEvent) => {
this.messages.push(`${e.type} (${e.x}, ${e.y})`);
});
}
}
7) lub tak:
@Component({
selector: 'watch-child-events',
template: `
<child-event-producer (event)="handle($event)">
Move over me!
</child-event-producer>
<div *ngFor="let message of messages">{{message}}</div>
`,
directives: [ChildEventProducerComponent]
})
export class WatchChildEventsComponent implements OnInit {
messages:string[] = [];
subject = new Subject<MouseEvent>();
ngOnInit() {
this.subject
.sampleTime(1000)
.subscribe((e:MouseEvent) => {
this.messages.push(`${e.type} (${e.x}, ${e.y})`);
});
}
handle(e:MouseEvent) {
this.subject.next(e);
}
}
oparciu o istniejącą Subject
, który jest identyczny do wcześniejszej techniki.
8) Wreszcie, jeśli potrzebujesz rozgłaszać powiadomienia w drzewie komponentów, usługa udostępniona wydaje się być drogą do zrobienia.
Zachowanie jest hermetyzowane w usłudze. Wszystko, co jest wymagane w komponencie potomnym, to LocationService
wstrzyknięty do konstruktora i wywołanie moveTo
w procedurze obsługi zdarzeń.
@Component({
selector: 'child-event-producer',
template: `
<p (mousemove)="handle($event)">
<ng-content></ng-content>
</p>
`
})
export class ChildEventProducerComponent {
constructor(private svc:LocationService) {}
handle(e:MouseEvent) {
this.svc.moveTo({x: e.x, y: e.y});
}
}
Wstrzyknij usługę na poziomie drzewa komponentów, z którego chcesz transmitować.
@Component({
selector: 'watch-child-events',
template: `
<child-event-producer>
Move over me!
</child-event-producer>
<div *ngFor="let message of messages">{{message}}</div>
`,
directives: [ChildEventProducerComponent],
providers: [LocationService]
})
export class WatchChildEventsComponent implements OnInit, OnDestroy {
messages:string[] = [];
subscription:Subscription;
constructor(private svc:LocationService) {}
ngOnInit() {
this.subscription = this.svc.stream
.subscribe((e:{x:number;y:number;}) => {
this.messages.push(`(${e.x}, ${e.y})`);
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Nie zapominając zrezygnować z subskrypcji po zakończeniu. To rozwiązanie oferuje dużą elastyczność kosztem pewnej złożoności.
Podsumowując, użyłbym tematu wewnętrznego dla komponentu, jeśli nie ma potrzeby komunikacji między komponentami (3). Gdybym potrzebował komunikować się z drzewem komponentu, hermetyzowałem obiekt w komponencie potomnym i stosowałem operatory strumienia w komponencie (5). W przeciwnym razie, jeśli potrzebowałbym maksymalnej elastyczności, użyłbym usługi do zawijania strumienia (8).
Zobacz również https://github.com/angular/angular/issues/10039 i https: // github.com/angular/angular/issues/4062. Myślę, że będzie to adresowane po wydaniu 2.0. –