위임 : EventEmitter 또는 Angular에서 관찰 가능


244

Angular에서 위임 패턴과 같은 것을 구현하려고합니다. 사용자가를 클릭 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!');
  }

}

핵심 질문은 관찰 구성 요소가 문제의 이벤트를 어떻게 관찰하도록하는 것입니까?


답변:


450

2016-06-27 업데이트 : Observable을 사용하는 대신 다음 중 하나를 사용하십시오.

  • 주석에서 @Abdulrahman이 권장하는 BehaviorSubject
  • 댓글에서 @Jason Goemaat가 추천 한 ReplaySubject

주제는 모두 피 감시 (우리가 할 수 있도록 subscribe()(우리가 호출 할 수 있도록하는 방법)과 옵저버 next()새 값을 방출하는 데에). 우리는이 기능을 이용합니다. 주체는 값을 많은 관찰자에게 멀티 캐스트 할 수 있습니다. 우리는이 기능을 이용하지 않습니다 (우리는 하나의 관찰자 만 있습니다).

BehaviorSubject 는 Subject의 변형입니다. "현재 값"이라는 개념이 있습니다. ObservingComponent를 만들 때마다 BehaviorSubject에서 현재 탐색 항목 값을 자동으로 가져옵니다.

아래 코드와 플런저 는 BehaviorSubject를 사용합니다.

ReplaySubject 는 Subject의 또 다른 변형입니다. 실제로 값이 생성 될 때까지 기다리려면을 사용하십시오 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


Observable을 사용하는 원래 답변 : (BehaviorSubject를 사용하는 것보다 더 많은 코드와 논리가 필요하므로 권장하지 않지만 유익 할 수 있습니다)

여기 EventEmitter 대신 Observable을 사용하는 구현이 있습니다. 내 EventEmitter 구현과 달리이 구현은 현재 navItem서비스에서 선택된 서비스를 저장 하므로 관찰 컴포넌트가 작성 될 때 API 호출을 통해 현재 값을 검색 navItem()한 후 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


옵저버 블 과 함께를 사용하는 Component Interaction Cookbook 예제 도 참조하십시오 Subject. 예제는 "부모 및 자식 통신"이지만 관련없는 구성 요소에 동일한 기술을 적용 할 수 있습니다.


3
Angular2 문제에 대한 의견에서 반복적으로 언급되었지만 EventEmitter출력 이외의 다른 곳 에서는 사용하지 않는 것이 좋습니다 . 그들은 현재이 연습을 장려하지 않기 위해 자습서 (서버 통신 AFAIR)를 다시 작성하고 있습니다.
Günter Zöchbauer

2
위의 샘플 코드에서 Navigation 구성 요소 내에서 서비스를 초기화하고 첫 번째 이벤트를 시작하는 방법이 있습니까? 문제는 네비게이션 컴포넌트가 호출 될 _observer때 서비스 객체의 서비스가 적어도 초기화되지 않는다는 ngOnInit()것이다.
ComFreek

4
Observable 대신 BehaviorSubject 를 사용하는 것이 좋습니다 . 그것은 가까이있어 EventEmitter그것의 "핫"이미이 현재의 값을 저장하도록 설계, "공유"는 무엇을 의미하고 마침내 구현 모두 관찰 가능한 및 옵저버 당신 코드의 최소 5 개 라인과 두 가지 속성 절약 할 수 있기 때문에
압둘 라흐만 Alsoghayer을

2
@PankajParkar, "변경 감지 엔진이 관찰 가능한 객체에서 변경이 발생했음을 어떻게 알 수 있습니까?"와 관련하여 이전 주석 응답을 삭제했습니다. 나는 최근 Angular가 monkey-patch가 아니라는 것을 알았 subscribe()으므로 관찰 가능한 변경 사항을 감지 할 수 없습니다. 일반적으로 비동기 이벤트가 발생합니다 (샘플 코드에서는 버튼 클릭 이벤트). 관련 콜백은 Observable에서 next ()를 호출합니다. 그러나 변경 감지는 관찰 가능한 변경이 아닌 비동기 이벤트로 인해 실행됩니다. Günter 의견도 참조하십시오 : stackoverflow.com/a/36846501/215945
Mark Rajcok

9
실제로 값이 생성 될 때까지 기다리려면을 사용할 수 있습니다 ReplaySubject(1). A BehaviorSubject는 즉시 제공 될 초기 값을 요구합니다. 는 ReplaySubject(1)항상 가장 최근의 값을 제공하지만 서비스가 그것의 첫 번째 값을 반환하기 전에 몇 가지 비동기 작업을 수행 할 수 있도록 초기 값이 필요하지 않습니다 만, 여전히 마지막 값 이후의 호출에 즉시 화재. 하나의 가치에만 관심이 있다면 first()구독에 사용할 수 있으며 마지막에 구독을 취소 할 필요가 없습니다.
Jason Goemaat

33

속보 : EventEmitter가 아닌 Observable을 사용하는 다른 답변 을 추가했습니다 . 나는 이것에 대한 대답을 권장합니다. 실제로 서비스에서 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이 설정 되도록 컴포넌트를 전달해야합니다

업데이트 : 두 번째 글 머리 기호를 해결하는 대안은 ObservingComponent가 navchangeEventEmitter 속성 을 직접 구독하도록하는 것입니다.

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

Zouabi가 제공 한 답변 보다이 솔루션을 선호하지만 솔직히이 솔루션의 팬이 아닙니다. 나는 파괴에 대한 구독 해지에 신경 쓰지 않지만 이벤트에 가입하기 위해 구성 요소를 전달해야한다는 사실을 싫어합니다 ...
the_critic

나는 실제로 이것에 대해 생각 하고이 솔루션으로 가기로 결정했습니다. 나는 약간 더 깨끗한 해결책을 갖고 싶지만 그것이 가능한지 확실하지 않습니다 (또는 아마도 내가 말해야 할보다 우아한 것을 생각해 낼 수 없을 것입니다).
the_critic

실제로 두 번째 글 머리 기호 문제는 함수에 대한 참조가 대신 전달된다는 것입니다. 수정 this.subscription = this.navService.subscribe(() => this.selectedNavItem());및 구독 :return this.navchange.subscribe(callback);
André Werlang

1

보다 반응성 지향적 인 프로그래밍 스타일을 따르기를 원한다면, "모든 것이 스트림이다"라는 개념이 분명히 나타나기 때문에 Observables를 사용하여 이러한 스트림을 가능한 한 자주 처리하십시오.


1

다음 중 하나를 사용할 수 있습니다.

  1. 행동 주제 :

BehaviorSubject는 주제의 한 유형이며, 주제는 관찰 가능한 역할을 할 수있는 특수한 유형의 관찰 가능하며 관찰자는 다른 관찰 가능과 마찬가지로 메시지를 구독 할 수 있으며 구독하면 소스 관찰 가능 개체가 방출 한 주제의 마지막 값을 반환합니다.

이점 : 구성 요소간에 데이터를 전달하는 데 필요한 부모-자식 관계와 같은 관계가 없습니다.

탐색 서비스

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

탐색 구성 요소

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

관찰 구성 요소

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

두 번째 접근법은 Event Delegation in upward direction child -> parent

  1. @Input 및 @Output 데코레이터 사용 부모가 자식 구성 요소에 자식 데이터를 전달하고 자식 구성 요소에 알림

예 : @Ashish Sharma가 제공 한 답변.


0

ObservingComponent 템플릿에서 Navigation 구성 요소를 사용해야합니다 (예 : Navigation 구성 요소에 선택기 를 추가하는 것을 잊지 마십시오 .

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

그리고 ObservingComponent에서 onNavGhange ()을 구현하십시오.

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

마지막으로 .. @Componennt 의 events 속성이 필요하지 않습니다

events : ['navchange'], 

기본 구성 요소에 대한 이벤트 만 연결합니다. 그것은 내가하려는 일이 아닙니다. 나는 (^ navchange) (캐럿은 이벤트 버블 링을위한 것입니다)과 같은 것을 말할 수 nav-item있었지만 다른 사람들이 관찰 할 수있는 이벤트를 내고 싶습니다.
the_critic

. 당신은) (가입 navchange.toRx ()를 사용할 수 있습니다 ...하지만 당신은 ObservingComponent에 navchange에 대한 참조가 필요합니다
무라드 Zouabi

0

위에서 설명한대로 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)
}

}

이제 observer.component.html이 Observer 컴포넌트의 뷰라고 가정 해 봅시다.

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

그런 다음 ObservingComponent.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);
 }

 }

-2

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


1
가장 좋은 대답은 subscribe 메소드를 사용하는 것입니다. 실제로 요즘에는 구성 요소 간의 통신 문제를 해결하기 위해 Redux 또는 다른 상태 제어를 사용하는 것이 좋습니다. 복잡성을 추가하지만 다른 솔루션보다 훨씬 우수합니다. Angular 2 구성 요소 이벤트 핸들러 sintax를 사용하거나 subscribe 메소드를 명시 적으로 사용하면 개념은 동일하게 유지됩니다. 마지막 생각은 그 문제에 대한 확실한 해결책을 원한다면 Redux를 사용하고, 그렇지 않으면 이벤트 이미 터와 함께 서비스를 사용하는 것입니다.
Nicholas Marcaccini Augusto

subscribe는 각도가 관측 가능하다는 사실을 제거하지 않는 한 유효합니다. .subscribe ()는 최상의 답변에 사용되지만 특정 객체에는 사용되지 않습니다.
Porschiey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.