1

I'm passing a value from a service to a component using BehaviorSubject -

In serviceFile.service.ts:

taskComplete = new BehaviorSubject<{ complete: Boolean; error: any }>(null);
...
this.taskComplete.next({ complete: false, error: error });
...

In componentFile.component.ts:

ngOnInit() {
    this.taskCompleteSub = this.myService.taskComplete.subscribe(
      (data) => {
            this.error = data.error
            ? data.error.error.message
            : null;
          console.log(this.error);
        }
    ); 
}

The problem is that the value of property this.error is changed and printed in console.log(), but this change is not reflected in the component template. In other words, angular does not check this change and re-render.

atiyar
  • 7,762
  • 6
  • 34
  • 75
Mahmoud
  • 60
  • 8
  • The question is how are you using that `this.taskComplete.next()` call in your service. Is that code getting executed at all, after the initial emit? Apparently the call is inside some method. Who is calling that method? Or did you put that inside the `constructor`? – atiyar Mar 28 '21 at 06:54
  • I put that method inside a callback – Mahmoud Mar 28 '21 at 19:19
  • I think I didn't understand the issue correctly. Is the changed value of `this.error` received and logged in the subscribe method as expected? – atiyar Mar 29 '21 at 02:13

3 Answers3

1

You are initializing your taskComplete BehaviorSubject with null, so that's the first value emitted. However, in your component you are trying to access data.error when data is null for the first value emitted. The following should work:

this.error = data && data.error && data.error.error
  ? data.error.error.message
  : null;

I created this working example: https://stackblitz.com/edit/angular-fh6cfg?file=src%2Fapp%2Fapp.component.ts

Isaac
  • 184
  • 9
1

If this.myService.taskComplete is an asynchronous action you'll need to manually trigger change detection.

constructor(private cdr: ChangeDetectorRef) { }

...


ngOnInit() {
    this.taskCompleteSub = this.myService.taskComplete.subscribe(
      (data) => {
            this.error = ...;
            this.cdr.markForCheck();
        }
    ); 
}
martin
  • 93,354
  • 25
  • 191
  • 226
1

I'd suggest two changes.

  1. If the default value of the BehaviourSubject is null and if you're forced to check if the value is null in each of it's subscription, you're better off using a ReplaySubject with buffer 1 instead. It'll buffer/hold the last value similar to BehaviorSubject but doesn't require a default value.

  2. If the object's underlying reference hasn't changed, the Angular change detection may detect any changes to re-render the template. In that case try to make a hard-copy using JSON.parse(JSON.stringify()).

Service

taskComplete = new ReplaySubject<{ complete: Boolean; error: any }>(1);

Component

ngOnInit() {
  this.taskCompleteSub = this.myService.taskComplete.subscribe(
    (data) => {
      // `null` check not required here now
      this.error = JSON.parse(JSON.stringify(data.error.error.message));
      console.log(this.error);
    }
  ); 
}
ruth
  • 29,535
  • 4
  • 30
  • 57