2016-08-02 17 views
11

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).

+1

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. –

Odpowiedz

1

W metodzie 6 można użyć zamiast @ViewChildevent binding (scroll down to "Custom Events with EventEmitter") i ngAfterViewInit, który upraszcza wiele:

@Component({ 
    selector: 'watch-child-events', 
    template: ` 
    <child-event-producer (event)="onEvent($event)"> 
     Move over me! 
    </child-event-producer> 
    <div *ngFor="let message of messages">{{message}}</div> 
    `, 
    directives: [ChildEventProducerComponent] 
}) 
export class WatchChildEventsComponent { 
    messages:string[] = []; 
    onEvent(e) { this.messages.push(`${e.type} (${e.x}, ${e.y})`); } 
} 
+0

Brakuje funkcji stosowania operatorów (sampleTime) do obserwowalnego strumienia zdarzeń emitowanych przez składnik podrzędny. W technice 5 komponent potomny hermetyzuje tę funkcjonalność, która pozwala na jej użycie tak, jak tutaj. – gwhn

Powiązane problemy