Delegacja: Zdarzenie lub obserwowalne w kątowym

Próbuję zaimplementować coś w rodzaju wzorca delegacji w Angular. Kiedy użytkownik kliknie na nav-item, chciałbym wywołać funkcję, która następnie emituje zdarzenie, które z kolei powinno być obsługiwane przez inny komponent nasłuchujący tego zdarzenia.

Oto scenariusz: mam Navigation Składnik:

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

}

Oto składowa obserwacyjna:

export class ObservingComponent {

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

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

}

Kluczowe pytanie brzmi: jak sprawić, by komponent Obserwujący obserwował dane zdarzenie ?

Author: yurzui, 2015-12-20

6 answers

Aktualizacja 2016-06-27: zamiast używać Obserwabli, użyj

  • BehaviorSubject, jak zaleca @Abdulrahman w komentarzu, lub
  • ReplaySubject, zgodnie z zaleceniami @ Jason Goemaat w komentarzu

A podmiot jest zarówno obserwowalnym (więc możemy subscribe() do niego), jak i obserwatorem (więc możemy wywołać next() na nim, aby emitować nową wartość). Wykorzystujemy tę funkcję. Obiekt pozwala wartościom być multicastem dla wielu obserwatorów. Nie wykorzystujemy tej funkcji (mamy tylko jednego obserwatora).

BehaviorSubject jest odmianą podmiotu. Ma pojęcie "bieżącej wartości". Wykorzystujemy to: ilekroć tworzymy ObservingComponent, automatycznie pobiera on aktualną wartość elementu nawigacyjnego z obiektu BehaviorSubject.

Poniższy kod iplunker używają BehaviorSubject.

ReplaySubject jest innym wariantem tematu. Jeśli chcesz poczekać, aż wartość zostanie faktycznie Wyprodukowana, użyj ReplaySubject(1). Podczas gdy BehaviorSubject wymaga wartości początkowej (która zostanie dostarczona natychmiast), ReplaySubject nie. ReplaySubject zawsze dostarczy najnowszą wartość, ale ponieważ nie ma wymaganej wartości początkowej, usługa może wykonać pewną operację asynchroniczną przed zwróceniem pierwszej wartości. Nadal będzie uruchamiany natychmiast po kolejnych wywołaniach z najnowszą wartością. Jeśli chcesz tylko jedną wartość, użyj first() w subskrypcji. Nie musisz rezygnować z subskrypcji, jeśli używasz 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


Oryginalna odpowiedź, która używa obserwowalnego: (wymaga więcej kodu i logiki niż używanie BehaviorSubject, więc nie polecam, ale może być pouczająca)

Oto implementacja, która używa obserwowalnego zamiast Eventemittera . W przeciwieństwie do mojej implementacji EventEmitter, implementacja ta przechowuje również aktualnie wybrane navItem w usłudze, dzięki czemu gdy komponent Obserwujący jest tworzony, może on pobrać bieżącą wartość za pomocą wywołania API navItem(), a następnie być powiadamiany o zmianach za pomocą navChange$ Observable.

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


Zobacz także przykład component Interaction Cookbook , który używa Subject oprócz obserwabli. Chociaż przykładem jest "komunikacja rodziców i dzieci", ta sama technika ma zastosowanie do niepowiązanych elementów.

 370
Author: Mark Rajcok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:26

Z OSTATNIEJ CHWILI: dodałem inną odpowiedź która używa obserwowalnego zamiast Eventemittera. Polecam tą odpowiedź. W rzeczywistości używanie Eventemittera w usłudze jest złą praktyką.


Oryginalna odpowiedź: (nie rób tego)

W przypadku, gdy nie jest to możliwe, nie jest to możliwe, ponieważ nie jest to możliwe, ponieważ nie jest to możliwe.]}

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

Jeśli spróbujesz Plunker, jest kilka rzeczy, których nie lubię w tym podejściu:

  • ObservingComponent musi zrezygnować z subskrypcji, gdy zostanie zniszczony
  • musimy przekazać komponent do subscribe() tak, aby właściwa this została ustawiona, gdy wywołanie zwrotne zostanie wywołane

Update: alternatywą, która rozwiązuje 2. bullet jest posiadanie ObservingComponent bezpośrednio subskrybować navchange właściwość EventEmitter:

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

Jeśli SUBSKRYBUJEMY bezpośrednio, to nie potrzebowałbym metody subscribe() w NavService.

Aby uczynić NavService nieco bardziej zamkniętym, możesz dodać metodę getNavChangeEmitter() i użyć tego:

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

constructor(private navService:NavService) {  // in ObservingComponent
   this.subscription = this.navService.getNavChangeEmitter().subscribe(data =>
     this.selectedNavItem(data));
}
 30
Author: Mark Rajcok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:26

Jeśli ktoś chce podążać za bardziej reaktywnym zorientowanym stylem programowania, to zdecydowanie pojawia się pojęcie "wszystko jest strumieniem" i dlatego używa Obserwabli, aby radzić sobie z tymi strumieniami tak często, jak to możliwe.

 1
Author: kg11,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-18 04:37:56

Możesz użyć BehaviourSubject jak opisano powyżej lub jest jeszcze jeden sposób:

Możesz obsługiwać EventEmitter w ten sposób: najpierw dodaj selektor

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

}

Teraz możesz obsłużyć to wydarzenie jak Załóżmy, że obserwator.komponent.html to Widok komponentu Observer

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

Następnie w Obserwacjiskładnik.ts

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

 }
 1
Author: Ashish Sharma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-05 09:43:27

Musisz użyć komponentu Nawigacyjnego w szablonie ObservingComponent (nie zapomnij dodać selektora do komponentu nawigacyjnego .. nawigacja-komponent dla ex)

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

I zaimplementować onnavghange () w ObservingComponent

onNavGhange(event) {
  console.log(event);
}
Ostatnia rzecz .. nie potrzebujesz atrybutu events w @ Componennt
events : ['navchange'], 
 0
Author: Mourad Zouabi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-20 10:23:59

Znalazłem inne rozwiązanie tej sprawy bez korzystania z usług Reactivex ani. Uwielbiam rxjx API, jednak myślę, że najlepiej sprawdza się przy rozwiązywaniu asynchronicznych i/lub złożonych funkcji. Używając go w ten sposób, jest dla mnie dość przekroczony.

Myślę, że szukasz transmisji. Tylko to. I znalazłem takie rozwiązanie:
<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))
   }
 }

To jest pełny przykład pracy: https://plnkr.co/edit/xGVuFBOpk2GP0pRBImsE

 -2
Author: Nicholas Marcaccini Augusto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-13 12:51:03