Jessica Shannon
Jessica Shannon

Reputation: 361

How do I subscribe to changes in an angular model on an observable?

I'm loading a list of items over http and using a BehaviorSubject to stream the updates from an angular service to a component. The items have a boolean on them which is bound to a checkbox.

I want another component to use the same service and subscribe to the same data source, but only show the items that are checked. This code below works fine when the initial data loads. But if the checkbox in the first component is checked, it doesn't update the second component.

Do I need to manually update the object from the first component on check and push it back into the observer?

Full example on stackblitz here

export class MyService {

    private mySubject = new BehaviorSubject<MyModel[]>([]);

    allObservable: Observable<MyModel[]>  = this.mySubject.asObservable();

    selectedObservable = this.allObservable.pipe(map(result => result.filter(f => f.selected)));

    constructor() {
        this.fundsSubject.next([
          { selected: true },
          { selected: false },
        ]);
    }
}

export class MyModel {

  public selected: boolean = false;

}

Upvotes: 5

Views: 17165

Answers (4)

SiddAjmera
SiddAjmera

Reputation: 39432

Even if you're making changes to the data in the AppComponent, your MyService is not aware of the change. Hence it is not pushing new data to the subscribers.

To make it aware of the change, just make the following changes to the following files:

AppComponent Template:

<ul>
  <li *ngFor="let row of data">
     <input (change)="onChange()" type="checkbox" [(ngModel)]="row.selected"/> {{row.name}} 
    </li>
</ul>
<hr/>
Selected:
<hello></hello>

This change will actually listen to the change in data. Notice the (change) handler added to the input tag.

AppComponent Class:

import { Component, OnInit } from '@angular/core';
import { MyService, MyModel } from './service.component';

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

  constructor(private myService : MyService){}
  
  ngOnInit() {
    this.myService.allObservable.subscribe(d => this.data = d);
  }

  onChange() {
    this.myService.setUpdatedData(this.data);
  }

}

This change will propagate the change to the service by calling the setUpdatedData on it.

And add this method to the MyService:

setUpdatedData(data) {
  this.mySubject.next(data);
}

This change will propagate the change to all the subscribers.


Here's a Working StackBlitz Sample For your ref.

Upvotes: 2

Mickers
Mickers

Reputation: 1449

When you subscribe to your observable you can just add a pipe filter and take only the checked items. Example:

import { filter } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';

let _data: BehaviorSubject<Array<string>> = new BehaviorSubject(['']);

_data.subscribe((data) => console.log(1, data)); // takes all results

_data.pipe(filter((stuff: Array<string>) => stuff.some((item) => item === 'batman'))
).subscribe((result) => console.log(2, result)); // only takes results that pass the check

_data.next(['justice']); // test 1
_data.next(['justice', 'batman']); // test 2

Upvotes: 0

erhise
erhise

Reputation: 311

There is all lot of things going on in your project that can be improved, but basically you need to notify your observer that a change has taken place, there are several way to do this. I have just added one example "that works", without saying it is a good solution.

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

// app.component.html
<input ... (change)="notifyChange()"/>

// app.component.ts
public notifyChange(): void {
    this.myService.updateData(this.data);
}

// service.component.ts (preferable named something.service.ts)
public updateData(newData: MyModel[]): void {
  this.mySubject.next(newData);
}

Hope this helps!

Upvotes: 1

Aragorn
Aragorn

Reputation: 5289

If you are importing the service in both the components, angular gives separate instances of the service for each of the components. Consider adding the service in the provider scope.

You can use the annotation to do that:

@Injectable({
  providedIn: 'root',
})

Look for documentation here

Upvotes: 0

Related Questions