GeForce RTX 4090
GeForce RTX 4090

Reputation: 3498

Observable next() callback not triggered

I'm trying to implement a global loading indicator that can be reused in the entire application. I have an injectable service that has the show and hide functions:

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

@Injectable()
export class SpinnerOverlayService {
    private loaderSubject = new Subject<any>();


    public loaderState = this.loaderSubject.asObservable();

    constructor() { }

    /**
     * Show the spinner
     */
    show(): void {
        this.loaderSubject.next(<any>{ show: true });
    }

    /**
     * Hide the spinner
     */
    hide(): void {
        this.loaderSubject.next(<any>{ show: false });
    }
}

And this is the code of the spinner overlay component. I'll exclude details about the HTML and CSS implementation as they're not important here.

import { Component, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { SpinnerOverlayService } from '../spinner-overlay.service';

@Component({
  selector: 'spinner-overlay',
  templateUrl: './spinner-overlay.component.html',
  styleUrls: ['./spinner-overlay.component.scss']
})
export class SpinnerOverlayComponent implements OnInit {

  show = false;

  private _subscription: Subscription;

  constructor(private spinnerOverlayService: SpinnerOverlayService) { }

  ngOnInit(): void {
    this._subscription = this.spinnerOverlayService.loaderState.subscribe((state) => {
        console.log("Subscription triggered.");
        this.show = state.show;
      });
  }

  ngOnDestroy(): void {
    this._subscription.unsubscribe();
  }
}

The problem: In the code of the overlay component I'm subscribing to the observable loaderState of the service. However when I call the show() function which triggers the next() of the observable, the subscription callback is not triggered.

This is how I call the show() function in the app.component.ts:

ngOnInit() {
               this.spinnerOverlayService.show();
}

What could I be missing? Seems really strange that the callback is not triggered.

Here is an example in Stackblitz: https://stackblitz.com/edit/angular-7-registration-login-example-2qus3f?file=app%2Fspinner-overlay%2Fspinner-overlay.component.ts

Upvotes: 4

Views: 4212

Answers (2)

Lia
Lia

Reputation: 11982

In addition to the above answer you can have a state in your spinnerOverlayService service to check the show hide and also have a subject to subscribe if new value is ready:

public state = { show: false };

    constructor() { }

    /**
     * Show the spinner
     */
    show():void {
      this.state = { show: true };
      this.loaderSubject.next(<any>{ show: true })
    }

    /**
     * Hide the spinner
     */
    hide():void {
      this.state = { show: false };
      this.loaderSubject.next(<any>{ show: false })
    }

and in your ngOnInit:

 ngOnInit(): void {
    if(this.spinnerOverlayService.state.show){
      console.log('Subscription triggeredd.');
    };
    this._subscription = this.spinnerOverlayService.loaderState.subscribe((state) => {
        console.log("Subscription triggered.");
        this.show = state.show;
      });
  }

OR you can use:

private loaderSubject = new ReplaySubject(1); // to cache last value

demo.

Upvotes: 2

Bunyamin Coskuner
Bunyamin Coskuner

Reputation: 8859

The problem is you call this.spinnerOverlayService.show(); before spinner-overlay is initialized. Subjects do not hold previous emitted value, so late subscribers won't get any value unless there is a new value.

One thing you can do is to change Subject to BehaviorSubject which emits the last value to new subscribers.

Or, you can call this.spinnerOverlayService.show(); within ngAfterViewInit. This way, you'll know spinner-overlay will get initialized and subscribe to spinnerOverlayService.loaderState

ngAfterViewInit() {
  this.spinnerOverlayService.show();
}

Check it out

Upvotes: 4

Related Questions