Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Service with Subject Observable calling next() is not seen by Component subscriptions

I want to retrieve an updated object value (borrower) from an Angular Service. I've created an RxJS Subject in the Service so that multiple components can subscribe to it and get the updated value.

When I subscribe to the Subject through the provided service in the component, I find that the onNext() function argument is never called even when .next() is called from BorrowerService.

If I did subscribe within the BorrowerService class, I find that .next() works as expected. It seems that the Subject is a different instance between the Service class that calls .next() and the component, and so doesn't get that subscription.

Is this a failing in understanding Angular Services or Observables? How can I update the value in the Service and passively get the changes in the Component without it knowing when to call .next()?

borrower-report.component.ts

@Component({
    ...
    providers:   [BorrowerService]
})
export class BorrowerReport {
    private borrower: Borrower;

    constructor(private borrowerService: BorrowerService) {
        this.borrowerService.borrowerChanged.subscribe(borrower => {this.borrower = borrower});
    }
}

borrower.service.ts

@Injectable()
export class BorrowerService {
    public borrowerChanged: Subject<Borrower> = new Subject<Borrower>();

    //event handler
    selectBorrower(borrower: Borrower) {
        this.borrowerChanged.next(borrower);
    }
}
like image 294
MattTreichel Avatar asked Dec 23 '22 13:12

MattTreichel


2 Answers

The problem is the scope of your Service, and that a @ngModule is not providing the service. If you want this to be a singleton service (same service instance for multiple components), then you will want to provide the service in a parent module that contains all the children components. For a guaranteed route, add this to your app.module.ts. Import it in the module, and provide it as a service as you have in your component (remove it as a provider from your component, though... you only want the parent module to provide it).

You can then import the service into your component as you already have, and the provider will be the parent module. The basic idea is the the parent module will host the service for all the children components, and therefore all the children components will receive the same instance of the service (and therefore the same values).

Side note: This SO Answer provides the details on creating a singleton service call ApiService and should work as a solid guide. The other top answer provides a solid explanation as well.

Second note: If you want push updates from your subject, you will want want to use a BehaviorSubject as well. Subscriptions to BehaviorSubject will update automatically, while subscriptions to Subjects must be called manually.

like image 112
Z. Bagley Avatar answered Dec 26 '22 01:12

Z. Bagley


Consider the following :

borrower.service.ts

import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs';


@Injectable()
export class BorrowerService {

  public borrowerChanged = new BehaviorSubject<string>('Defaut Value');
  borrowerChangedObservable = this.borrowerChanged.asObservable();
  constructor(){}

  changeValue(value: string): void {
    this.borrowerChanged.next(value);
  }
}

and then in your component:

borrower-report.component.ts

to get the values you do:

constructor(private borrowerService: BorrowerService) {
    this.borrowerService.borrowerChangedObservable
       .subscribe((borrower) => {
              console.log(borrower);
          });
    }

to set a new value you do:

sendNewValueToBorrowerService(){
this.borrowerService.changeValue('my new value');
}

and don't forget to add the service in the provides array and the component in the declarations array of the same module.

like image 37
Hamed Baatour Avatar answered Dec 26 '22 01:12

Hamed Baatour