Delegasyon: EventEmitter veya Açısal Olarak Gözlenebilir


244

Angular'da delegasyon kalıbı gibi bir şey uygulamaya çalışıyorum. Kullanıcı a'yı tıklattığında, nav-itemdaha sonra olayı dinleyen başka bir bileşen tarafından ele alınması gereken bir olay yayan bir işlevi çağırmak istiyorum.

İşte senaryo: Bir Navigationbileşen var:

import {Component, Output, EventEmitter} from 'angular2/core';

@Component({
    // other properties left out for brevity
    events : ['navchange'], 
    template:`
      <div class="nav-item" (click)="selectedNavItem(1)"></div>
    `
})

export class Navigation {

    @Output() navchange: EventEmitter<number> = new EventEmitter();

    selectedNavItem(item: number) {
        console.log('selected nav item ' + item);
        this.navchange.emit(item)
    }

}

İşte gözlem bileşeni:

export class ObservingComponent {

  // How do I observe the event ? 
  // <----------Observe/Register Event ?-------->

  public selectedNavItem(item: number) {
    console.log('item index changed!');
  }

}

Anahtar soru şudur: Gözlemleme bileşeninin söz konusu olayı nasıl gözlemlemesini sağlarım?


Yanıtlar:


450

2016-06-27 Güncellemesi: Gözlemlenebilirler kullanmak yerine aşağıdakilerden birini kullanın:

  • @Abdulrahman tarafından bir yorumda önerildiği gibi bir BehaviorSubject veya
  • @Jason Goemaat tarafından bir yorumda önerildiği gibi bir ReplaySubject

Bir Konu hem bir Gözlemlenebilir (bunu yapabiliriz subscribe()) hem de bir Gözlemcidir (böylece next()yeni bir değer yaymak için onu çağırabiliriz ). Bu özellikten yararlanıyoruz. Bir Konu, değerlerin birçok Gözlemci için çok noktaya yayın olmasına izin verir. Bu özellikten faydalanmıyoruz (sadece bir Gözlemcimiz var).

BehaviorSubject , Subject'nin bir varyantıdır. "Geçerli değer" kavramına sahiptir. Bundan yararlanıyoruz: Bir ObservingComponent oluşturduğumuzda, geçerli gezinme öğesi değerini BehaviorSubject öğesinden otomatik olarak alır.

Aşağıdaki kod ve dalgıç BehaviorSubject kullanır.

ReplaySubject , Subject'nin başka bir çeşididir. Bir değer üretilinceye kadar beklemek istiyorsanız, kullanın ReplaySubject(1). Bir BehaviorSubject bir başlangıç ​​değeri gerektirdiğinden (hemen sağlanacaktır), ReplaySubject gerektirmez. ReplaySubject her zaman en son değeri sağlar, ancak gerekli başlangıç ​​değerine sahip olmadığından, hizmet ilk değerini döndürmeden önce bazı zaman uyumsuz işlemler yapabilir. En son değere sahip sonraki aramalarda hemen tetiklenir. Sadece bir değer istiyorsanız first(), aboneliği kullanın . Kullanırsanız abonelikten çıkmanız gerekmez first().

import {Injectable}      from '@angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

@Injectable()
export class NavService {
  // Observable navItem source
  private _navItemSource = new BehaviorSubject<number>(0);
  // Observable navItem stream
  navItem$ = this._navItemSource.asObservable();
  // service command
  changeNav(number) {
    this._navItemSource.next(number);
  }
}
import {Component}    from '@angular/core';
import {NavService}   from './nav.service';
import {Subscription} from 'rxjs/Subscription';

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  subscription:Subscription;
  constructor(private _navService:NavService) {}
  ngOnInit() {
    this.subscription = this._navService.navItem$
       .subscribe(item => this.item = item)
  }
  ngOnDestroy() {
    // prevent memory leak when component is destroyed
    this.subscription.unsubscribe();
  }
}
@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
    <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>`
})
export class Navigation {
  item = 1;
  constructor(private _navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this._navService.changeNav(item);
  }
}

Plunker


Gözlemlenebilir kullanan orijinal yanıt: (BehaviorSubject kullanmaktan daha fazla kod ve mantık gerektirir, bu yüzden tavsiye etmiyorum, ancak öğretici olabilir)

Yani, burada EventEmitter yerine Gözlemlenebilir kullanan bir uygulama . EventEmitter uygulamamın aksine, bu uygulama o anda seçili navItemolan hizmeti de depolar , böylece bir gözlemleme bileşeni oluşturulduğunda, geçerli değeri API çağrısı yoluyla alabilir navItem()ve ardından navChange$Gözlemlenebilir aracılığıyla değişikliklerden haberdar edilebilir .

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/share';
import {Observer} from 'rxjs/Observer';

export class NavService {
  private _navItem = 0;
  navChange$: Observable<number>;
  private _observer: Observer;
  constructor() {
    this.navChange$ = new Observable(observer =>
      this._observer = observer).share();
    // share() allows multiple subscribers
  }
  changeNav(number) {
    this._navItem = number;
    this._observer.next(number);
  }
  navItem() {
    return this._navItem;
  }
}

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  subscription: any;
  constructor(private _navService:NavService) {}
  ngOnInit() {
    this.item = this._navService.navItem();
    this.subscription = this._navService.navChange$.subscribe(
      item => this.selectedNavItem(item));
  }
  selectedNavItem(item: number) {
    this.item = item;
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
    <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>
  `,
})
export class Navigation {
  item:number;
  constructor(private _navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this._navService.changeNav(item);
  }
}

Plunker


Ayrıca, gözlemlenebilirlere ek olarak kullanılan Bileşen Etkileşimi Yemek Kitabı örneğine bakın Subject. Örnek "ebeveyn ve çocuk iletişimi" olmasına rağmen, aynı teknik ilişkisiz bileşenler için de geçerlidir.


3
Angular2 konularının yorumlarında, EventEmitterçıktılar dışında herhangi bir yerde kullanılmasının önerilmediği tekrar tekrar belirtildi . Şu anda bu uygulamayı teşvik etmemek için öğreticileri (sunucu iletişimi AFAIR) yeniden yazıyorlar.
Günter Zöchbauer

2
Yukarıdaki örnek kodda Gezinti bileşeninin içinden hizmeti başlatmanın ve ilk etkinliği durdurmanın bir yolu var mı? Sorun, _observerservis nesnesinin ngOnInit()çağrıldığında Gezinti bileşeninin en azından başlatılmamış olmasıdır .
ComFreek

4
Observable yerine BehaviorSubject kullanmanızı öneririm . Bu yakın içelim EventEmitterO en "sıcak" zaten o anki değerini kaydetmek için tasarlanmış, "paylaşılan" oluyor anlam ve nihayet uygulayan hem gözlemlenebilir ve Gözlemci seni kodunun en az beş hatları ve iki özellik kazandıracak çünkü
Abdulrahman Alsoghayer

2
@PankajParkar, "nasıl değişiklik algılama motoru değişiklik gözlemlenebilir nesnede olduğunu biliyor" ile ilgili - Önceki yorum yanıtımı sildim. Son zamanlarda Angular'ın maymun yaması olmadığını öğrendim subscribe(), bu yüzden gözlemlenebilir bir değişiklik olduğunda bunu tespit edemez. Normalde, (örnek kodumda, düğme tıklama olaylarıdır) tetiklenen bazı eşzamansız olaylar vardır ve ilişkili geri arama, gözlemlenebilir bir durumda next () öğesini çağırır. Ancak değişiklik algılama, gözlemlenebilir değişiklik nedeniyle değil, zaman uyumsuz olay nedeniyle çalışır. Ayrıca bkz. Günter yorumları: stackoverflow.com/a/36846501/215945
Mark Rajcok

9
Bir değer üretilinceye kadar beklemek istiyorsanız kullanabilirsiniz ReplaySubject(1). A BehaviorSubjectderhal sağlanacak bir başlangıç ​​değeri gerektirir. Her ReplaySubject(1)zaman en son değeri sağlar, ancak başlangıç ​​değeri gerekli değildir, böylece hizmet ilk değerini döndürmeden önce bazı zaman uyumsuz işlemler yapabilir, ancak yine de son değere sahip sonraki çağrılarda hemen tetiklenir. Sadece bir değerle ilgileniyorsanız first(), abonelikte kullanabilirsiniz ve sonunda abonelikten çıkmanız gerekmez, çünkü bu tamamlanacaktır.
Jason Goemaat

33

Flaş haber: Ben ekledim başka bir yanıt , bir EventEmitter yerine bir gözlemlenebilir kullanır. Bunun cevabını tavsiye ederim. Ve aslında, bir hizmette EventEmitter kullanmak kötü bir uygulamadır .


Orijinal cevap: (bunu yapma)

EventEmitter'ı, ObservingComponent öğesinin etkinliğe doğrudan abone olmasını (ve abonelikten çıkmasını) sağlayan bir hizmete yerleştirin :

import {EventEmitter} from 'angular2/core';

export class NavService {
  navchange: EventEmitter<number> = new EventEmitter();
  constructor() {}
  emit(number) {
    this.navchange.emit(number);
  }
  subscribe(component, callback) {
    // set 'this' to component when callback is called
    return this.navchange.subscribe(data => call.callback(component, data));
  }
}

@Component({
  selector: 'obs-comp',
  template: 'obs component, index: {{index}}'
})
export class ObservingComponent {
  item: number;
  subscription: any;
  constructor(private navService:NavService) {
   this.subscription = this.navService.subscribe(this, this.selectedNavItem);
  }
  selectedNavItem(item: number) {
    console.log('item index changed!', item);
    this.item = item;
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">item 1 (click me)</div>
  `,
})
export class Navigation {
  constructor(private navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this.navService.emit(item);
  }
}

Eğer denerseniz, Plunkerbu yaklaşımla ilgili sevmediğim birkaç şey var:

  • ObservingComponent, yok edildiğinde aboneliğini iptal etmelidir
  • geri arama çağrıldığında subscribe()doğru thisayarlanacak şekilde bileşeni iletmeliyiz

Güncelleme: İkinci madde işaretini çözen bir alternatif, ObservingComponent navchangeöğesinin doğrudan EventEmitter özelliğine abone olmasını sağlamaktır :

constructor(private navService:NavService) {
   this.subscription = this.navService.navchange.subscribe(data =>
     this.selectedNavItem(data));
}

Doğrudan abone olursak subscribe(), NavService'te yönteme ihtiyacımız olmazdı.

NavService'i biraz daha kapsüllenmiş yapmak için bir getNavChangeEmitter()yöntem ekleyebilir ve bunu kullanabilirsiniz:

getNavChangeEmitter() { return this.navchange; }  // in NavService

constructor(private navService:NavService) {  // in ObservingComponent
   this.subscription = this.navService.getNavChangeEmitter().subscribe(data =>
     this.selectedNavItem(data));
}

Bu çözümü Bay Zouabi'nin verdiği cevaba tercih ediyorum, ama dürüst olmak gerekirse bu çözümün de hayranı değilim.
Yıkımdan ayrılmayı umursamıyorum

Aslında bunu düşündüm ve bu çözümle devam etmeye karar verdim. Biraz daha temiz bir çözüme sahip olmak isterdim, ancak bunun mümkün olduğundan emin değilim (veya muhtemelen söylemem gereken daha zarif bir şey bulamıyorum).
the_critic

aslında 2. mermi problemi, bunun yerine Fonksiyona bir referansın iletilmesidir. düzeltmek için: this.subscription = this.navService.subscribe(() => this.selectedNavItem());ve abone olun:return this.navchange.subscribe(callback);
André Werlang

1

Eğer kişi daha Reaktif odaklı bir programlama tarzını takip etmek istiyorsa, kesinlikle "Her şey bir akıştır" kavramı ortaya çıkar ve bu nedenle bu akışlarla mümkün olduğunca sık ilgilenmek için Gözlemlenebilirler kullanın.


1

Şunlardan birini kullanabilirsiniz:

  1. Davranış Konu:

BehaviorSubject bir tür öznedir, bir özne, gözlemlenebilir olarak hareket edebilen özel bir gözlemlenebilir türdür ve gözlemlenebilir diğer iletiler gibi mesajlara abone olabilirsiniz ve abonelik üzerine, gözlemlenebilir kaynak tarafından yayılan öznenin son değerini döndürür:

Avantaj: Bileşenler arasında veri iletmek için ebeveyn-çocuk ilişkisi gibi bir ilişki gerekmez.

NAV HİZMETİ

import {Injectable}      from '@angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

@Injectable()
export class NavService {
  private navSubject$ = new BehaviorSubject<number>(0);

  constructor() {  }

  // Event New Item Clicked
  navItemClicked(navItem: number) {
    this.navSubject$.next(number);
  }

 // Allowing Observer component to subscribe emitted data only
  getNavItemClicked$() {
   return this.navSubject$.asObservable();
  }
}

NAVİGASYON BİLEŞENİ

@Component({
  selector: 'navbar-list',
  template:`
    <ul>
      <li><a (click)="navItemClicked(1)">Item-1 Clicked</a></li>
      <li><a (click)="navItemClicked(2)">Item-2 Clicked</a></li>
      <li><a (click)="navItemClicked(3)">Item-3 Clicked</a></li>
      <li><a (click)="navItemClicked(4)">Item-4 Clicked</a></li>
    </ul>
})
export class Navigation {
  constructor(private navService:NavService) {}
  navItemClicked(item: number) {
    this.navService.navItemClicked(item);
  }
}

GÖZLEM BİLEŞENİ

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  itemClickedSubcription:any

  constructor(private navService:NavService) {}
  ngOnInit() {

    this.itemClickedSubcription = this.navService
                                      .getNavItemClicked$
                                      .subscribe(
                                        item => this.selectedNavItem(item)
                                       );
  }
  selectedNavItem(item: number) {
    this.item = item;
  }

  ngOnDestroy() {
    this.itemClickedSubcription.unsubscribe();
  }
}

İkinci Yaklaşım Event Delegation in upward direction child -> parent

  1. Alt öğeye veri ileten @Input ve @Output dekoratörleri üst öğelerini ve üst öğeye alt bildiren alt öğeyi kullanma

@ Cevapladı @Ashish Sharma.


0

ObservingComponent şablonunda Gezinme bileşenini kullanmanız gerekir ( Gezinme bileşenine bir seçici eklemeyi unutmayın. Ex için gezinme bileşeni)

<navigation-component (navchange)='onNavGhange($event)'></navigation-component>

Ve ObservingComponent öğesinde onNavGhange () yöntemini uygulayın

onNavGhange(event) {
  console.log(event);
}

Son şey .. @Componennt'deki olaylar özelliğine ihtiyacınız yok

events : ['navchange'], 

Bu yalnızca temel bileşen için bir olayı bağlar. Yapmaya çalıştığım bu değil. Ben sadece (^ navchange) (caret olay köpürmek içindir) gibi bir şey söyleyebilirdim nav-itemama sadece başkalarının gözlemleyebileceği bir olay yayınlamak istiyorum.
the_critic

navchange.toRx (). abone ol () .. kullanabilirsiniz ancak ObservingComponent
Mourad Zouabi 20:15

0

BehaviourSubject öğesini yukarıda açıklandığı gibi kullanabilirsiniz veya başka bir yol daha vardır:

EventEmitter'ı şu şekilde işleyebilirsiniz: önce bir seçici ekleyin

import {Component, Output, EventEmitter} from 'angular2/core';

@Component({
// other properties left out for brevity
selector: 'app-nav-component', //declaring selector
template:`
  <div class="nav-item" (click)="selectedNavItem(1)"></div>
`
 })

 export class Navigation {

@Output() navchange: EventEmitter<number> = new EventEmitter();

selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this.navchange.emit(item)
}

}

Şimdi observer.component.html dosyasının Observer bileşeninin görünümü olduğunu varsayalım gibi bu olayı işleyebilirsiniz

<app-nav-component (navchange)="recieveIdFromNav($event)"></app-nav-component>

sonra ObservingComponent.ts içinde

export class ObservingComponent {

 //method to recieve the value from nav component

 public recieveIdFromNav(id: number) {
   console.log('here is the id sent from nav component ', id);
 }

 }

-2

Reactivex hizmetlerini kullanmadan bu durum için başka bir çözüm buldum. Aslında ben async ve / veya karmaşık bir işlevi çözerken en iyi gider düşünüyorum rxjx API seviyorum. Bu şekilde kullanarak, bu oldukça beni aştı.

Aradığınızı düşündüğüm bir yayın. Sadece bu. Ve bu çözümü buldum:

<app>
  <app-nav (selectedTab)="onSelectedTab($event)"></app-nav>
       // This component bellow wants to know when a tab is selected
       // broadcast here is a property of app component
  <app-interested [broadcast]="broadcast"></app-interested>
</app>

 @Component class App {
   broadcast: EventEmitter<tab>;

   constructor() {
     this.broadcast = new EventEmitter<tab>();
   }

   onSelectedTab(tab) {
     this.broadcast.emit(tab)
   }    
 }

 @Component class AppInterestedComponent implements OnInit {
   broadcast: EventEmitter<Tab>();

   doSomethingWhenTab(tab){ 
      ...
    }     

   ngOnInit() {
     this.broadcast.subscribe((tab) => this.doSomethingWhenTab(tab))
   }
 }

Bu tam çalışan bir örnektir: https://plnkr.co/edit/xGVuFBOpk2GP0pRBImsE


1
En iyi yanıta bakın, abone olma yöntemini de kullanıyor .. Aslında bugün bileşenler arasında bu iletişim problemini çözmek için Redux veya başka bir durum kontrolü kullanmanızı tavsiye ederim. Ekstra karmaşıklık katmasına rağmen sonsuzluk diğer çözümlerden çok daha iyidir. Angular 2 bileşenleri olay işleyicisi sintax'ı kullanarak veya açıkça abone olma yöntemini kullanarak konsept aynı kalır. Son düşüncelerim, bu sorun için kesin bir çözüm istiyorsanız, Redux kullanın, aksi takdirde olay gönderen hizmetleri kullanın.
Nicholas Marcaccini Augusto

abone olduğu sürece açısal gözlemlenebilir olduğu gerçeğini kaldırmazsa geçerlidir. .subscribe () en iyi yanıtta kullanılır, ancak belirli bir nesnede kullanılmaz.
Porschiey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.