3
votes

e(1): Updated code to address typo's in original question.
e(2): Attempted to 'JSON.stringify()' the data pushed into my JSON object in 'createContent()'. No luck.
e(3): Added plnkr as requested: http://plnkr.co/edit/j024MYCQbypAGaf762fT

I have a model...

class Content {
  constructor(public name: string) { }
}

I have a service...

class ContentService {
  private _contents$: Subject<Content[]>
    = new Subject<Content[]>();

  get contents$(): Observable<Content[]> {
    return this._contents$.asObservable();
  }

  private _data: { contents: Content[] } = { contents: [] };

...

  load(): void {
    createContent(new Content("Test Content..."));
  }

  createContent(item: Content): void {
    this._data.contents.push(item);
    this._contents$.next(this._data.contents);
  }
}

I have a component...

@Component({
...
template: `
<h1 *ngFor="let item of contents$ | async">{{ item.name }}</h1>
`
})
class ContentListComponent {
  contents$: Observable<Content[]>;

  constructor(private _contentService: ContentService) { }

  ngOnInit(): void {
    this.contents$ = this._contentService.contents$;
    this._contentService.load();
  }
}

Simply put, I don't understand why the Observable in my component is not functioning correctly.

If I change these around, such that I have an array in the component, subscribe to the service 'contents$' to populate the array, then it's no biggie.

When I log the output of the service and functions called, everything is also dandy. The only symptom I am experiencing is that the "*ngFor" does not appear to be properly handling the stream. It's something obvious, isn't it?

2
I think this is the problem: this.contents$ = this._contentService.contents$;. Please try this in your template: *ngFor="let item of _contentService.contents$ | async" - rinukkusu
Can you please try to move this line ` this.contents$ = this._contentService.contents$;` into the contructor? I haven't tried it but maybe the view doesn't like if contents$ is null when it first tries to resolve the bindings. - Günter Zöchbauer
@rinukkusu: The component's backing object has a property named 'contents$' as well. That was my mistake naming these similarly for this question. I did attempt it however, sourcing the service directly. Nothing, unfortunately. - Dustin Holtz
@GünterZöchbauer: Nice thought. I moved it to the constructor, but the output is the same. Still no dice. - Dustin Holtz
A Plunker would be helpful - Günter Zöchbauer

2 Answers

4
votes

The problem is that Angular evaluates the

<h1 *ngFor="let item of contents$ | async">{{ item.name }}</h1>

only when change detection runs the next time.

Because

this._service.load();

is executed sync, the event is emitted before Angular had a chance to subscribe.

If you for example use a BehaviorSubject instead of Subject which emits that last emitted value to new subscribers, then you get the desired behavior.

Plunker example

You could also invoke change detection manually before calling this._service.load(); or call this._service.load(); delayed
but that depends on your specific requirements.

0
votes

I see a potential problem in your code:

createContent(item: Content): void {
  this._data.contents$.push(item);
  this._contents$.next(this._data.contents$);
}

I would use the following instead:

createContent(item: Content): void {
  this._data.contents.push(item); // <----
  this._contents$.next(this._data.contents); // <----
}