menu

Questions & Answers

Angular observable updating boolean and object

I have a service with two observables, one returns an array of numbers and another one a single boolean. If I try to udate the data after the subscription, pushing a new value to the array of numbres and change the boolean, the related view will display the updated numbers but the boolean remains the same.

here is the code on Stackblitz:

https://stackblitz.com/edit/angular-empty-project-qqkucy?%20%20%20%20%20%20file=app/test.service.ts,app/app.component.ts,app/app.component.html&file=app%2Fapp.component.ts

=====================service
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable()
export class TestService {

private myNumbers:number[]=[Math.random()]
myBoolean:boolean=false

updateData(){
this.myNumbers.push(Math.random())
this.myBoolean=!this.myBoolean
}

getNumbers(): Observable<number[]> {
return Observable.of(this.myNumbers);
}

getBoolean():Observable<boolean>{
this.myBoolean=!this.myBoolean
return  Observable.of(this.myBoolean);
}
}
=================component

import { Component, OnInit } from '@angular/core';
import {TestService} from './test.service';

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

export class AppComponent implements OnInit {
name = 'Angular 5';

constructor(private dataService:TestService){}

myNumbers:number[]=[];
myBoolean:boolean|undefined=undefined;

ngOnInit() {

this.dataService.getNumbers().subscribe({
next:(value)=>this.myNumbers=value
})

this.dataService.getBoolean().subscribe({
  next:(val:boolean)=>this.myBoolean=val
})

}

subscribe_bool(){
this.dataService.getBoolean().subscribe({
next:(val)=>this.myBoolean=val
})
}

update_data(){
this.dataService.updateData();
}
}
=============view

<br>
<button (click)="update_data()">Update data</button> 
Push data in array and change boolean in service     (does not update the boolean in the view )
<br>
<br>
<button (click)="subscribe_bool()">new subscribe boolean</button> new subscribe (change the view)
<hr>
<hr>

<div *ngFor="let n of myNumbers;let i=index">
item {{i}}: {{n}}
</div>

boolean data: {{myBoolean}}

I can't understand why the array of numbers is updated in the view but not the boolean. If I make a new subscription the boolean value is updated. Is it something related with stream? Thanks!

Answers(2) :
  1. The array updates, because it's an object, therefore it's the same object everywhere. If you skipped subscriptions, made the field public, and just declared this.myNumbers = this.dataService.myNumbers; in the component, it would still have kept updating. The boolean, OTOH, is a primitive.
  2. You aren't using observables correctly. return Observable.of(this.myNumbers);, return Observable.of(this.myBoolean); just return a current value of the variable, when explicitly called, and when you call update_data, nothing changes for the streams. Just wrapping a value with an observable with of() doesn't make it reactive. If you want a live link, do sth like this:
export class TestService {
  private myNumbers: number[] = [];
  private myBoolean: boolean = false;
  private myNumbers$: Subject<number[]> = new BehaviorSubject([]);
  private myBoolean$: Subject<boolean> = new BehaviorSubject(false);

  updateData() {
    this.myNumbers = [...this.myNumbers, Math.random()];
    this.myNumbers$.next(this.myNumbers);
    this.myBoolean$.next((this.myBoolean = !this.myBoolean));
  }

  getNumbers(): Observable<number[]> {
    return this.myNumbers$.asObservable();
  }

  getBoolean(): Observable<boolean> {
    return this.myBoolean$.asObservable();
  }

https://stackblitz.com/edit/angular-empty-project-6sd5ca?%20%20%20%20%20%20file=app/test.service.ts,app/app.component.ts,app/app.component.html&file=app%2Fapp.component.html,app%2Fapp.component.ts,app%2Ftest.service.ts

One issue is that you're using of in your service. of closes the stream after it's done, emitting the values you give it as an argument. From the RxJS docs: Emit variable amount of values in a sequence and then emits a complete notification.

I am also confused by the use of next in your component. Here is a working version that updates both observables every time. I am using BehaviorSubject so that there is an initial value when the component subscribes to the observables.

Conponent:

  constructor(private dataService: TestService) {}

  myNumbers: number[] = [];
  myBoolean: boolean | undefined = undefined;

  ngOnInit() {
    this.dataService.myBoolean$.subscribe((val) => {
      this.myBoolean = val;
    });

    this.dataService.myNumbers$.subscribe((val) => {
      this.myNumbers = val;
    });
  }

  update_data() {
    this.dataService.updateData();
  }
}

In the service:

@Injectable()
export class TestService {
  private myNumbers: number[] = [Math.random()];
  myBoolean: boolean = false;
  myBoolean$ = new BehaviorSubject<boolean>(this.myBoolean);
  myNumbers$ = new BehaviorSubject<number[]>(this.myNumbers);

  updateData() {
    this.myNumbers.push(Math.random());
    this.myBoolean = !this.myBoolean;
    this.myBoolean$.next(this.myBoolean);
    this.myNumbers$.next(this.myNumbers);
  }
}

🚀 StackBlitz 🚀