0
votes

I have a component that prints an observable value (test$) to the template via the async pipe.

The component property needs to be initialised based on the component's inputs, so I am assigning its value to an observable emitted by a service (test$) in ngOnInit. The observable exposed by the service is assigned to a combination of subjects when the service is initialised. The value is not printed in the template. Stackblitz

If I define the combined subjects as BehaviorSubject, the template is notified of the new value.

I presume this has something to do with cold/hot observables. It is my understanding that if you subscribe to a BehaviorSubject you will always get the latest value even if you subscribed after it emitted a value, but with cold observables (as Subject) you need to subscribe before the value is emitted in order to be notified.

So why is the template not being updated if the subscription takes place before the subjects emit a value? My reasoning is that the subscription takes place when the template has been rendered, which is in ngOnInit. The subjects don't emit their values until after this step.

Component

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {

  @Input() initialValue: number;
  result$: Observable<number>;

  constructor(private service: TestService) {

  }

  ngOnInit() {
    console.log('component init');
this.result$ = this.service.result$;
// Get data based on inputs
this.service.initTransformedValue(this.initialValue);
  }


}

Service

@Injectable()
export class TestService {


  result$: Observable<number>;
  otherValue$: Observable<number>;
  transformedValue$: Observable<number>;

  constructor() {
    console.log('service constructor');
    this.init();
  }

  init(){
    this.result$ = combineLatest(
      this.transformedValue$,
      this.otherValue$
    ).pipe(map(([first, second]) => {
        console.log('have combined value');
        return first + second;
    })
    );
  }

  initTransformedValue(initialValue) {
    // Use timeout to simulate HTTP calls
    setTimeout(() => {
      console.log('service. emit transformedValue value');
      this.transformedValue$ = of(initialValue * 2);
    }, 1000);

setTimeout(() => {
      console.log('service. emit otherValue value');
      this.otherValue$ = of(initialValue * 4);
    }, 1200);



  }

}

Template

<p>{{result$ | async}}</p>
1

1 Answers

0
votes

You are creating a new observable with of, you need to make the current observable emit a new value.

https://stackblitz.com/edit/angular-fta9h1

You should never reassign an observable with another observable, you should make the existing observable emit, subjects and behaviour subjects can emit by calling next.

const { of, BehaviorSubject } = rxjs;

let obs = of('of initial value');

obs.subscribe(val => { console.log(val); });

// This is a new observable and will not effect the subscription to a different obsevable
obs = of('of new value');

const bs = new BehaviorSubject('BehaviorSubject initial value');

bs.subscribe(val => { console.log(val); });

// This emits a new value on the same observable
bs.next('BehaviorSubject new value');
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>