3
votes

I have a custom BehaviourSubject in my Angular app, that is used as an Observable in my template.

When I call next on the Subject I expect the Subject in the template to be resolved. I use the async pipe to listen to asynchronous changes in this Subject.

I created an example in Stackblitz to show the problem that I have: https://stackblitz.com/edit/angular-kmou5e

In app.component.ts I created an example asynchronous method, that sets a value to this.data after a timeout. As soon as this.data is set, hello.component.ts is initialized and rendered. I listen to OnChangesin HelloComponent and call next() on the custom Subject within HelloComponent.

As you can see in the template of HelloComponent I expect the observed Subject customSubject$ to be rendered as an <h1> as soon as is gets a value through next().

You can see, that the subscription of the customSubject$ is called, as shown in the logs. Only the template doesn't render this Observable/Subject.

How can I get the template to render when using a custom Subject? Do I need to use a different pipe?

FYI: I also tried to assign the value of the Subject to a new Observable using combineLatest, but it didn't work as well.

2
Change new Subject() to new ReplaySubject(1) or new BehaviorSubject(). learnrxjs.io/subjects/behaviorsubject.htmlReactgular

2 Answers

2
votes

It's a timing issue. Just change your Subject to a Behavior Subject (which caches the last emitted value) and your template will display the latest emitted value.

enter image description here

Here is a good resource on the various types of subjects.

1
votes

Very good question. The problem with this is the order of lifecycle hooks Subject does not hold values, it just multi-casts to subscribers if there aren't any tough luck. Now life cycle runs in this order. Constructor, ngOnChanges, OnNgInit ..... then it loads the views and child components. So for you to see this value you need to buffer it by using a BehaviorSubject.

To see what I am talking about

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { combineLatest, Observable, Subject } from 'rxjs';

@Component({
  selector: 'hello',
  template: `<h1>{{ customSubject$ | async }}</h1>
             <h2>{{test}}</h2>
  `
})
export class HelloComponent implements OnChanges  {
  @Input() name: string;
public customSubject$: Subject<string>;

    public test ='Before ngOnChanges run';

  constructor() {
      console.log('constructed');

    this.customSubject$ = new Subject();
    this.customSubject$.subscribe(s => {
      console.log('customSubject$ subscription resolved with ' + s);
    });
  }

  ngOnChanges(changes: SimpleChanges) {
    if (changes.hasOwnProperty('name')) {
      console.log('ngOnChanges is called with ' + this.name);
      this.customSubject$.next(this.name);

    }
          this.test ='After ngOnChanges Ran';
  }
}

You have a test variable its value is changed in ngOnChanges. You will never get a glimpse of the value 'Before ngOnChanges run' because the view was not initialized yet.

Now your *ngIf='data' is confusing you further because the component is not constructed before ngIf resolves to true

It helps to increase your delay to see this

  ngOnInit() {
    setTimeout(() => {
      this.data = 'some string';
    }, 5000);
  }