angulartypescriptrxjseventemitterasync-pipe

What is wrong with my Angular EventEmitter subscription via the async pipe?


The setup

The problem

My code

Here is a simplified version of my code - which I have verified is plagued by the same issue (I have omitted the imports and @Component declarations for simplicity, but they are in my code).

export class Tester implements OnChanges {
  @Input() id: string;
  public id$ = new EventEmitter<string>();

  public boundToIdUpdates$ = this.id$.pipe(
    tap((id) => console.log('boundToIdUpdate$ was subscribed to for id', id)),
    switchMap((id) => of(id + ' binding'))
  );

  ngOnChanges(changes: SimpleChanges): void {
    // Set `id$` var to emit update on every change of the input.
    if (!!changes.id) {
      const {currentValue, previousValue} = changes.id;
      if (!!currentValue && currentValue !== previousValue) {
        this.id$.emit(currentValue);
        console.log('emitting', currentValue);
      }
    }
  }
}

<div class="card">
  <div class="card-header">Case {{ id }}</div>
  <div class="card-block">
    <div class="card-title">
      @if (boundToIdUpdates$ | async; as boundId) {
        Bound ID: {{ boundId }}
      } @else {
        No Bound ID Available
      }
    </div>
  </div>
</div>

Every time, without fail, I see the following:

At this point I'm certain I'm missing something obvious, have found a deeper issue with Angular/RXJS, or am taking crazy pills. Help?


Solution

  • RxJs could be tricky. Here is what happens:

    This line:

    public boundToIdUpdates$ = this.id$.pipe(
        tap((id) => console.log('boundToIdUpdate$ was subscribed to for id', id)),
        switchMap((id) => of(id + ' binding'))
      );
    

    returns AnonymousSubject. Same as a regular Subject it does not return last value to the new subscription. First ngOnChanges call happens before the view is actually initialised, which means that this.id$.emit(currentValue) emits new value before boundToIdUpdates$ | async subscription is created. So async pipe is actually missed the emission and will never get a value before the new one comes.

    I am not sure why do you want to use EventEmitter as a source observable. A simple:

    public id$ = new BehaviorSubject<string>('');
    

    should help.