3

Consider this simple snippet of angular2/rxjs/typescript

  public rooms: Observable<Room[]>;      

  constructor ( ... ) {

    this.rooms = this.inspectShipSubject
      .do(() => console.log('foo'))
      .switchMap(ship => this.roomByShipService.getRoomsByShip(ship));

    //this.rooms.subscribe(); // <-- [1]
  }

Here's the template:

<some-component *ngFor="let room of rooms | async" [room]="room"></some-component>

It doesn't work ('foo' is not printed and no data shows) unless I uncomment the line indicated [1]. Why doesn't the template trigger the observable to execute automatically?

Is there a better way than to use a blank subscribe?

6
  • 1
    Can you try putting the expresion (rooms | async) into parenthesis? Commented Aug 18, 2017 at 13:52
  • 1
    Try moving the this.rooms initialization from your constructor to ngOnInit() Commented Aug 18, 2017 at 13:58
  • @martin Thanks, I tried that and it didn't make a difference. I'm not sure which is correct because many examples used both styles. Commented Aug 18, 2017 at 13:58
  • 1
    Related: stackoverflow.com/a/35302622/1919228. The async pipe already adds a subscription Commented Aug 18, 2017 at 14:01
  • @AndreiMatracaru Thanks, I tried this with the above suggestion and didn't see a difference. Commented Aug 18, 2017 at 14:01

2 Answers 2

3

OK, I'm not sure why, but the trick is to use BehaviourSubject instead of Subject on this.inspectShipSubject.

This works nicely and is triggered properly by the template.

  public rooms$: Observable<Room[]>;

  private inspectShipSubject: BehaviorSubject<Ship> = new BehaviorSubject<Ship>(null);

  constructor(
    ...
  ) { }

  ngOnInit() {
    this.rooms$ = this.inspectShipSubject.switchMap(ship => this.roomByShipService.getRoomsByShip(ship));
  }

  @Input()
  set ship(ship: Ship) {
    this.inspectShipSubject.next(ship);
  }

  get ship(): Ship {
    return this.inspectShipSubject.getValue();
  }

And the template:

<some-component *ngFor="let room of (rooms | async)" [room]="room"></some-component>
Sign up to request clarification or add additional context in comments.

4 Comments

Oh, you were using a Subject for inspectShipSubject...should have realized that from the name :). To better understand the difference to a BehaviorSubject, have a look at this article
@AndreiMatracaru, so why did it make the difference here?
Please do explain :)
If it's a regular Subject, and you emit a value before you subscribe to it, the subscriber won't get that value. It will only get values emitted after it subscribed. So in your case it probably wouldn't be triggered until the first change happens. Subjects only relay values as they are emitted. Whereas, a BehaviorSubject additionally stores a 'current' value, and will send that value to new subscribers. You must provide an initial 'current' value when you construct the BehaviorSubject (often null), as you did. ReplaySubject is another option you could look into. But I'm out of space
0

I'm not sure if this is a good solution (or just a workaround), but you could store every event in an array and then loop the array:

this.rooms=[];
this.roomsObservable = this.inspectShipSubject
      .switchMap(ship => this.roomByShipService.getRoomsByShip(ship))
      .observe(next =>this.rooms.push(next));

Edit: I have just realized that I'm not sure if you want to show a list of rooms or just the latest received room. In case you just need the latest one, then I think you don't need a ngFor directive.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.