Делегирование: EventEmitter или наблюдаемый в угловом



Я пытаюсь реализовать что-то вроде шаблона делегирования в угловой.
Когда пользователь нажимает на nav-item, Я хотел бы вызвать функцию, которая затем выдает событие, которое, в свою очередь, должно обрабатываться каким-либо другим компонентом, прослушивающим событие.



вот сценарий: у меня есть Navigation компоненты:





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

}


вот компонент наблюдения:



export class ObservingComponent {

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

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

}


ключевой вопрос заключается в том, как заставить компонент наблюдения наблюдать за событием в вопрос ?

673   6  

6 ответов:

обновление 2016-06-27: вместо использования наблюдаемых, используйте либо

  • A BehaviorSubject, как рекомендовано @Abdulrahman в комментарии, или
  • ReplaySubject, как рекомендовано @Jason Goemaat в комментарии

A теме является как наблюдаемым (так что мы можем subscribe() к нему) и наблюдатель (так мы можем назвать next() на нем выдавать новое значение). Мы используем эту функцию. Субъект позволяет значениям быть многоадресная рассылка для многих наблюдателей. Мы не используем эту функцию (у нас есть только один наблюдатель).

BehaviorSubject это вариант темы. Он имеет понятие "текущая стоимость". Мы используем это: всякий раз, когда мы создаем ObservingComponent, он автоматически получает текущее значение элемента навигации из объекта BehaviorSubject.

код ниже и plunker использовать BehaviorSubject.

ReplaySubject is еще один вариант темы. Если вы хотите дождаться фактического получения значения, используйте ReplaySubject(1). В то время как BehaviorSubject требует начального значения (которое будет предоставлено немедленно), ReplaySubject этого не делает. ReplaySubject всегда будет предоставлять самое последнее значение, но поскольку оно не имеет необходимого начального значения, служба может выполнить некоторую асинхронную операцию перед возвратом первого значения. Он по-прежнему будет срабатывать сразу при последующих вызовах с самым последним значением. Если вы просто хотите один значение, используйте first() на подписку. Вы не должны отказаться от подписки, если вы используете 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


оригинальный ответ, который использует наблюдаемые: (это требует больше кода и логики, чем использование BehaviorSubject, поэтому я не рекомендую его, но это может быть поучительно)

Итак, вот реализация, которая использует заметную вместо an EventEmitter. В отличие от моей реализации EventEmitter, эта реализация также хранит выбранный navItem в службе, чтобы при создании компонента наблюдения он мог получить текущее значение через вызов API navItem(), а затем получать уведомления об изменениях через navChange$ наблюдаема.

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


см. также пример поваренной книги взаимодействия компонентов, который использует a Subject в дополнение к наблюдаемым. Хотя примером является" связь родителей и детей", тот же метод применим для несвязанных компонентов.

последние новости: я добавил еще один ответ который использует наблюдаемый, а не EventEmitter. Я рекомендую этот ответ над этим. И на самом деле, использование EventEmitter в сервисе-это плохая практика.


оригинальный ответ: (не делайте этого)

поместите EventEmitter в службу, которая позволяет ObservingComponent напрямую подписаться (и отписаться) на событие:

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

если вы попытаетесь Plunker, есть несколько вещей, которые мне не нравятся в этом подходе:

  • ObservingComponent должен отписаться, когда он будет уничтожен
  • мы должны передать компонент в subscribe() Так что правильная this устанавливается при вызове обратного вызова

Update: альтернативой, которая решает 2-й пуля, является непосредственное наблюдение за компонентом подпишитесь на navchange свойство EventEmitter:

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

если мы подписываемся напрямую, то нам не понадобится subscribe() метод на NavService.

чтобы сделать NavService немного более инкапсулированным, вы можете добавить getNavChangeEmitter() метод и использовать это:

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

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

Если кто-то хочет следовать более реактивно ориентированному стилю программирования, то, безусловно, концепция "все является потоком" входит в картину и, следовательно, использовать наблюдаемые для работы с этими потоками как можно чаще.

вы можете использовать BehaviourSubject, как описано выше или есть еще один способ:

вы можете обрабатывать EventEmitter, как это: добавить селектор

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

}

теперь вы можете обрабатывать это событие, как предположим наблюдателя.деталь.html-это представление компонента Observer

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

затем в ObservingComponent.ТС

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

 }

вы должны использовать навигационный компонент в шаблоне ObservingComponent ( не забудьте добавить селектор в навигационную составляющую .. навигация-компонент для Ex )

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

и реализовать onNavGhange () в ObservingComponent

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

последнее дело .. вам не нужен атрибут события в @Componennt

events : ['navchange'], 

Я нашел другое решение для этого случая без использования Reactivex ни один из сервисов. Я действительно люблю RXJX API, однако я думаю, что это лучше всего подходит при разрешении асинхронной и/или сложной функции. Используя его таким образом, его довольно превзошел меня.

что я думаю, что вы ищете для трансляции. Только это. И я нашел это решение:

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

Это полный рабочий пример: https://plnkr.co/edit/xGVuFBOpk2GP0pRBImsE

Comments

    Ничего не найдено.