19

Does anyone know why this code (initializing a value from Subject) does not work? Is there a bug or by design? What am I doing wrong?

ts

import { Component, OnInit } from '@angular/core';
import { Subject } from "rxjs";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.styl']
})
export class AppComponent implements OnInit {
  itemSupplier$: Subject<any[]> = new Subject<any[]>();

  items: any[] = [
    {name: 'Item 1', value: 'item1'},
    {name: 'Item 2', value: 'item2'},
  ];

  ngOnInit(){
    this.itemSupplier$.next(this.items);
  }
}

html

<ul>
    <li *ngFor="let item of itemSupplier$ | async">{{item.name}}</li>
</ul>
Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
user776686
  • 7,933
  • 14
  • 71
  • 124
  • 3
    Could you expand on *"does not work"*? – jonrsharpe Jan 15 '17 at 23:45
  • Why would you do this? You already know that you want to emit items, then use a BehaviourSubject or, alternatively, refer directly to items instead of itemsSupplier$. Looks a lot like a race condition to me. – hogan Apr 23 '20 at 06:12

4 Answers4

18

It seems like a timing issue, if you throw it in a setTimeout it works.

setTimeout(() => this.itemSupplier$.next(this.items), 0)

EDIT

It is actually a better idea to use BehaviorSubject. This will provide the last value when it is subscribed to.

smoyer
  • 7,932
  • 3
  • 17
  • 26
  • Yes, in fact I discovered it right after I posted my question, but still it is not clear to me why I should resort to using `setTimeout` in this particular case. – user776686 Jan 16 '17 at 00:17
  • 4
    I've debugged it angular, and it looks like the async pipe doesn't register the subscription until AFTER ngOnInit, so you call next without it being subscribed. – smoyer Jan 16 '17 at 00:37
  • Oh, thanks for your effort! Do you believe it should be reported to angular or rx team as a bug? Or should I use AfterInit instead? – user776686 Jan 16 '17 at 08:12
  • I'm not 100% sure it's a bug. This isn't exactly how you'd use Observables, since no async stuff is happening. You'd just bind to that raw array rather than use an async pipe. I'm sure if you submit a bug to angular though they could tell you better than me. – smoyer Jan 16 '17 at 14:12
  • That's not quite possible to avoid async pipe. What I posted is a simplified version to mimic and test data change detection, but my target app is based on rxjs/store where the data is coming from store. In store I think everything is an observable. Will try to investigate. – user776686 Jan 16 '17 at 14:26
  • 14
    Changing `Subject` to `BehaviorSubject` as in: `itemSupplier$: BehaviorSubject = new BehaviorSubject([]);` does work. – user776686 Jan 16 '17 at 21:12
  • I was wondering if there were better solutions to this problem. Or is this still the way to go with ng 15? – heaxyh May 05 '23 at 15:43
1

I was having the same issue and setTimeout was effective as a solution, but found that I did not need to use setTimeout if an Observable to which the Subject's switchMap() method output was assigned was subscribed to PRIOR to calling next(). In your example, it might require creating an Observable, subscribing and updating the property directly rather than using the async pipe.

ajs
  • 11
  • 2
  • thank you! your answer helped me understand the nature of Subject's value emission and I could pinpoint the core issue I was having. – Islombek Hasanov Aug 08 '21 at 11:45
1

In case Google brought you here, but your problem is different, I removed the service from the Providers array, and it worked.

  • Superb, I was facing the same and tried all possible ways but your solution worked in my case. Thanks for posting this answer. – CodeChanger Aug 25 '23 at 11:39
0

You could make your next call into the afterViewinit hook. The async pipe will be subscribe to the Subject at this moment

Scandinave
  • 1,388
  • 1
  • 17
  • 41