我正在使用帶有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,對於大多數場景來說它似乎過度殺傷。
這種情況是,模板的元素髮出大量的事件,我想週期性地知道最近的值是什麼。
我使用Observable
的sampleTime
方法,週期爲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)。
參見https://github.com/angular/angular/issues/10039和https:// github.com/angular/angular/issues/4062。我想這將在2.0版本後解決。 –