gpt4 book ai didi

typescript - 如何从 Angular2 组件模板子元素事件创建 RxJS 可观察流

转载 作者:太空狗 更新时间:2023-10-29 17:03:34 25 4
gpt4 key购买 nike

我将 Angular 2.0.0-rc.4 与 RxJS 5.0.0-beta.6 一起使用。

我正在尝试从事件中创建可观察流的不同方法,但我对这些选项感到不知所措并想征求意见。我很欣赏没有一刀切的解决方案,并且类(class)中有马。可能还有其他我不知道或没有考虑过的技术。

Angular 2 component interaction cookbook提供了父组件与子组件事件交互的几种方法。但是,只有parent and children communicate via a service的例子使用 observables 并且在大多数情况下似乎有点矫枉过正。

场景是模板的元素发出大量事件,我想定期知道最新的值是什么。

我用 ObservablesampleTime以 1000ms 为周期监控鼠标位置的方法 <p> HTML 元素。

1) 此技术使用 ElementRef注入(inject)组件的构造函数以访问 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生命周期钩子(Hook)。

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: '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 连接到父级中装饰器要么像这样:
@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 ,这与早期的技术相同。

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注入(inject)构造函数并调用 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});
}
}

在需要广播的组件树级别注入(inject)服务。
@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();
}
}

完成后不要忘记取消订阅。此解决方案提供了很大的灵活性,但会增加一些复杂性。

总之,如果不需要组件间通信,我将使用组件内部的 Subject (3)。如果我需要向上组件树进行通信,我会将一个主题封装在子组件中,并在组件内应用流运算符 (5)。否则,如果我需要最大的灵活性,我会使用服务来包装流 (8)。

最佳答案

在方法 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})`); }
}

关于typescript - 如何从 Angular2 组件模板子元素事件创建 RxJS 可观察流,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38722621/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com