2016-08-02 30 views
11

Tôi đang sử dụng Angular 2.0.0-rc.4 với RxJS 5.0.0-beta.6.Cách tạo luồng RxJS quan sát được từ sự kiện phần tử con mẫu thành phần Angular2

Tôi đang thử nghiệm các phương pháp tạo luồng quan sát khác nhau từ các sự kiện, nhưng bị choáng ngợp bởi các tùy chọn và muốn phân phối ý kiến. Tôi đánh giá cao không có giải pháp một kích thước phù hợp và có những con ngựa cho các khóa học. Có thể có những kỹ thuật khác mà tôi không biết hoặc chưa xem xét.

Angular 2 component interaction cookbook cung cấp một số phương pháp của thành phần gốc tương tác với sự kiện của thành phần con. Tuy nhiên, chỉ có ví dụ về parent and children communicate via a service sử dụng các quan sát và có vẻ như quá mức cần thiết cho hầu hết các trường hợp.

Kịch bản là yếu tố của mẫu phát ra khối lượng sự kiện lớn và tôi muốn biết, định kỳ, giá trị gần đây nhất là gì.

Tôi sử dụng phương thức 's sampleTime với khoảng thời gian 1000ms để theo dõi vị trí chuột trên một phần tử HTML <p>.

1) Kỹ thuật này sử dụng số ElementRef được tiêm vào hàm tạo của thành phần để truy cập thuộc tính nativeElement và truy vấn các phần tử con theo tên thẻ.

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

Ý kiến ​​đồng ý dường như cau mày về kỹ thuật này vì Angular2 cung cấp đầy đủ trừu tượng DOM để bạn hiếm khi cần tương tác trực tiếp với DOM. Nhưng phương pháp nhà máy fromEvent của Observable làm cho nó khá hấp dẫn để sử dụng và nó là kỹ thuật đầu tiên mà đến tâm trí.

2) Kỹ thuật này sử dụng EventEmitter, là 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); 
    } 
} 

Giải pháp này tránh truy vấn DOM, nhưng bộ phát sự kiện được sử dụng để liên lạc từ trẻ sang bố mẹ và sự kiện này không dành cho đầu ra.

Tôi đọc here rằng không nên giả định rằng người phát sự kiện sẽ có thể quan sát được trong bản phát hành cuối cùng, vì vậy đây có thể không phải là tính năng ổn định để dựa vào.

3) Kỹ thuật này sử dụng Subject quan sát được.

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

Giải pháp này đánh dấu tất cả các hộp theo ý kiến ​​của tôi mà không thêm nhiều phức tạp. Tôi có thể sử dụng một số ReplaySubject để nhận toàn bộ lịch sử của các giá trị đã xuất bản khi tôi đăng ký hoặc chỉ có giá trị gần đây nhất nếu có, thay vào đó là subject = new ReplaySubject<MouseEvent>(1);.

4) Kỹ thuật này sử dụng tham chiếu mẫu cùng với trang trí @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})`); 
     }); 
    } 
} 

Trong khi nó hoạt động, nó có mùi một chút với tôi. Tham chiếu mẫu chủ yếu cho tương tác thành phần trong mẫu. Nó cũng chạm vào DOM qua nativeElement, sử dụng các chuỗi để tham chiếu đến tên sự kiện và tham chiếu mẫu và sử dụng móc vòng đời AfterViewInit.

5) Tôi đã mở rộng ví dụ để sử dụng thành phần tùy chỉnh quản lý Subject và phát ra sự kiện định kỳ.

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

Nó được sử dụng bởi phụ huynh như thế này:

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

Tôi thích kỹ thuật này; thành phần tùy chỉnh đóng gói hành vi mong muốn và làm cho nó dễ dàng cho phụ huynh sử dụng, nhưng nó chỉ truyền đạt cây thành phần và không thể thông báo cho anh chị em.

6) Tương phản với kỹ thuật này chỉ đơn giản là chuyển tiếp sự kiện từ trẻ sang cha mẹ.

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

Và là dây lên trong các phụ huynh bằng cách sử dụng @ViewChild trang trí hoặc như thế nà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] 
}) 
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) Hoặc như thế này:

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

sử dụng một thể quan sát được Subject, đó là giống hệt nhau với kỹ thuật trước đó.

8) Cuối cùng, nếu bạn cần phát sóng thông báo trên cây thành phần, dịch vụ được chia sẻ có vẻ là cách để đi.

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

Hành vi được đóng gói trong dịch vụ. Tất cả những gì được yêu cầu trong thành phần con là LocationService được tiêm vào hàm tạo và gọi hàm moveTo trong trình xử lý sự kiện.

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

Tiêm dịch vụ ở cấp độ của cây thành phần bạn cần phát sóng.

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

Không quên hủy đăng ký khi hoàn tất. Giải pháp này cung cấp rất nhiều sự linh hoạt tại các chi phí của một số phức tạp.

Tóm lại, tôi sẽ sử dụng Nội dung chủ đề cho thành phần nếu không cần truyền thông liên thành phần (3). Nếu tôi cần giao tiếp lên cây thành phần, tôi sẽ đóng gói một Subject trong thành phần con và áp dụng các toán tử stream trong thành phần (5). Nếu không, nếu tôi cần sự linh hoạt tối đa, tôi sẽ sử dụng một dịch vụ để bọc một luồng (8).

+1

Xem thêm https://github.com/angular/angular/issues/10039 và https: // github.com/angular/angular/issues/4062. Tôi đoán điều này sẽ được giải quyết sau khi phát hành 2.0. –

Trả lời

1

Trong phương pháp 6 bạn có thể sử dụng event binding (scroll down to "Custom Events with EventEmitter") thay vì @ViewChildngAfterViewInit, mà đơn giản hóa rất nhiều:

@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

Điều này thiếu chức năng áp dụng toán tử (sampleTime) cho luồng sự kiện quan sát được phát ra bởi thành phần con. Trong kỹ thuật 5, thành phần con đóng gói chức năng đó, cho phép nó được sử dụng giống như bạn có ở đây. – gwhn

Các vấn đề liên quan