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

MattTreichel picture MattTreichel · Oct 6, 2017 · Viewed 10k times · Source

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);
    }
}

Answer

Z. Bagley picture Z. Bagley · Oct 6, 2017

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.