2016-08-02 63 views
11

我正在使用帶有RxJS 5.0.0-beta.6的Angular 2.0.0-rc.4。如何從Angular2組件模板子元素事件創建RxJS可觀察流

我正在嘗試從事件創建可觀察流的不同方法,但是被選項所淹沒,並且想徵求意見。我很欣賞沒有一種適合所有人的解決方案,並且有適合課程的馬匹。可能還有其他技術我不知道或沒有考慮過。

Angular 2 component interaction cookbook提供了父組件與子組件的事件進行交互的幾種方法。但是,只有parent and children communicate via a service的示例使用了observables,對於大多數場景來說它似乎過度殺傷。

這種情況是,模板的元素髮出大量的事件,我想週期性地知道最近的值是什麼。

我使用ObservablesampleTime方法,週期爲1000ms,以監視HTML元素的<p>上的鼠標位置。

1)此技術使用注入到組件的構造函數中的ElementRef來訪問nativeElement屬性並通過標記名稱查詢子元素。

@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})`); 
     }); 
    } 
} 

共識的意見,似乎對這種技術皺眉,因爲Angular2提供了對DOM足夠的抽象,這樣你很少,如果有的話,需要直接與它交互。但fromEvent工廠方法Observable使它很誘人,並且它是第一個想到的技術。

2)該技術使用EventEmitter,這是一個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); 
    } 
} 

該解決方案避免了查詢DOM,但事件發射器用於從孩子溝通的父母,並且該事件不是輸出。

我讀了here,它不應該假定事件發射器在最終版本中是可觀察的,所以這可能不是一個穩定的特性依賴。

3)該技術使用可觀察的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); 
    } 
} 

這個解決方案在我看來用了所有的方框,卻沒有增加很多複雜性。我可以使用ReplaySubject來接收發布價值的完整歷史記錄,或者只是最近的一個記錄(如果存在),而是使用subject = new ReplaySubject<MouseEvent>(1);

4)該技術使用模板參考與裝飾器@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})`); 
     }); 
    } 
} 

雖然它的工作,它聞起來有點給我。模板引用主要用於模板中的組件交互。它還通過nativeElement觸及DOM,使用字符串來引用事件名稱和模板引用,並使用AfterViewInit生命週期掛鉤。

5)我擴展了該示例以使用管理Subject並定期發出事件的自定義組件。

@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); 
    } 
} 

它用於由父這樣的:

@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})`); 
    } 
} 

我喜歡這個技術;自定義組件封裝了所需的行爲並使其可以方便地使用父級,但它只會傳遞組件樹並且不能通知兄弟。

6)將這種技術簡單地將事件從小孩轉發給父母。

@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)或類似這樣的:

@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); 
    } 
} 

用觀察到的Subject,其是相同

@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); 
    } 
} 

並且使用@ViewChild裝飾或者像這樣在父有線了到較早的技術。 8)最後,如果你需要在整個組件樹上廣播通知,共享服務似乎是要走的路。

@Injectable() 
export class LocationService { 
    private source = new ReplaySubject<{x:number;y:number;}>(1); 

    stream:Observable<{x:number;y:number;}> = this.source 
    .asObservable() 
    .sampleTime(1000); 

    moveTo(location:{x:number;y:number;}) { 
    this.source.next(location); 
    } 
} 

該行爲被封裝在服務中。子組件中所需的全部內容是在構造函數中注入的LocationService以及在事件處理函數中調用moveTo

@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}); 
    } 
} 

在需要廣播的組件樹級別注入服務。

@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(); 
    } 
} 

完成後不要忘記取消訂閱。該解決方案以犧牲一些複雜性爲代價提供了很大的靈活性。

總之,如果不需要組件間通信(3),我會使用組件內部的主題。如果我需要溝通組件樹,我會在子組件中封裝一個Subject,並在組件(5)中應用流操作符。否則,如果我需要最大的靈活性,我會使用服務來封裝流(8)。

+1

參見https://github.com/angular/angular/issues/10039和https:// github.com/angular/angular/issues/4062。我想這將在2.0版本後解決。 –

回答

1

在方法6,您可以使用event binding (scroll down to "Custom Events with EventEmitter")代替@ViewChildngAfterViewInit,從而簡化了很多:

@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

這缺少將運算符(sampleTime)應用於子組件發出的可觀察事件流的功能。在技​​術5中,子組件封裝了該功能,從而可以像在這裏一樣使用它。 – gwhn