1

I don't understand why this does not work but the alternatives below do.

app.component.ts

import { Component } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { first } from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  id = 0;
  obs = new Subject();

  changeValue() {
    this.fakeApiCall(this.id++)
      .pipe(first())
      .subscribe(this.obs);
  }

  fakeApiCall(id: number) {
    return new Observable(observer => {
      console.log('called with id ' + id);
      observer.next({ items: [id, Math.random()] });
    });
  }
}

app.component.html

<h1>
    <div *ngIf="(obs | async) as v">
        <ul>
            <li *ngFor="let item of v.items">
                {{ item }}
            </li>
        </ul>

        <ul>
            <li *ngFor="let item of v.items">
                {{ item }}
            </li>
        </ul>
    </div>

    <div *ngIf="(obs | async) as v">
        <ul>
            <li *ngFor="let item of v.items">
                {{ item }}
            </li>
        </ul>

        <ul>
            <li *ngFor="let item of v.items">
                {{ item }}
            </li>
        </ul>
    </div>

    <button (click)="changeValue()">increase counter</button>
</h1>

When I'm clicking the "increase counter" button, it first gets values, but not afterward, while these alternatives work just fine:

changeValue() {
    this.fakeApiCall(this.id++)
      .subscribe(this.obs)
      .unsubscribe();
  }

or

changeValue() {
    this.fakeApiCall(this.id++)
      .pipe(take(2)) // NOTES: if I write take(1), it has the same effect.
      .subscribe(this.obs);
  }

I am confused is this a bug or what's really going on here?

EDIT: if you want stackblitz url, here: https://stackblitz.com/edit/angular-28eyiy

Nika
  • 1,864
  • 3
  • 23
  • 44

1 Answers1

1

This is not a bug, the problem is in the way you're subscribing Subject with subscribe(this.obs).

When you use subscribe(this.obs) you're passing all notifications to your Subject instance. This means next, complete and error notifications. And here's the problem, first() will complete the chain after the first emission (btw see Angular 2 using RxJS - take(1) vs first()) which means it'll pass one next and one complete notification. When Subject receives a complete notification its stopped and will never ever re-emit anything even when you subscribe it again to a different chain (this is a part of "The Observable Contract").

So if you want to use the same Subject in multiple subscriptions where you know they'll complete you can pass only the next notification and nothing more (Subject will stay active):

.subscribe(v => this.obs.next(v));
martin
  • 93,354
  • 25
  • 191
  • 226
  • ahh! good catch, never thought about the complete notification. nice job buddy and thank you! – Nika Mar 10 '19 at 11:23