4

I created a loading spinner component for my angular 4 app that should show during AJAX calls but I have trouble displaying it with a subscription to a BehaviorSubject.

This question is related to how to show a spinner till the time data recieved from the server in angular2 but not a duplicate (as I want to do it with a reusable component) and Angular 2 + RxJS BehaviorSubject subscribe call not working (Though I don't see any difference between my code and the accepted answer. Did I miss something?)

I basically followed this tutorial https://hassantariqblog.wordpress.com/2017/03/22/angular2-using-custom-loader-spinner-as-service-in-angular-2-application/

Here's some of my code:

app.modules.ts

import {AppComponent} from './app.component';
import {AppRoutingModule} from './app-routing.module';
import {SomeComponent} from './some.component';
import {LoaderComponent} from './loader/loader.component';
import {LoaderService} from './loader/loader.service';

@NgModule({
    declarations: [
        AppComponent,
        LoaderComponent,
        SomeComponent
    ],
    imports: [
        BrowserModule,
        NoopAnimationsModule,
        AppRoutingModule
    ],
    providers: [
        LoaderService
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

app.component.html

<router-outlet></router-outlet>
<app-loader></app-loader>

loader.component.html

<div class="loader" *ngIf="isLoading"></div>

loader.component.ts

import {Component, OnInit} from '@angular/core';
import {LoaderService} from './loader.service';

@Component({
    selector: 'app-loader',
    templateUrl: 'loader.component.html',
    providers: [LoaderService]
})

export class LoaderComponent implements OnInit {
    isLoading: boolean;

    constructor(private loaderService: LoaderService) {
    }

    ngOnInit() {
        this.loaderService.status.subscribe((val: boolean) => {
            this.isLoading = val;
        });
    }
}

loader.service.ts

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

@Injectable()
export class LoaderService {
    public status: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

    display(value: boolean) {
        console.log('LoaderService.display ' + value);
        this.status.next(value);
    }
}

A method in some service that does an AJAX call

constructor(private http: Http, private loaderService: LoaderService) {
}

getSomeStuff(): Observable<SomeItem[]> {
    // show the loading spinner
    this.loaderService.display(true);

    const content = this.http.get(this.apiUrl)
        .map(this.extractData);

    content.subscribe(
        () => {
            // hide the loading spinner
            this.loaderService.display(false);
        }
    );

    return content;
}

The problem is that the loader never gets shown, because isLoading is never set to true. The console output:

LoaderComponent subscription false
LoaderService.display true
LoaderService.display false

So the subscription to the loaderService, that changes the value of isLoading in loader.component.ts only gets called once, when initializing the component. But as far as I understand how BehaviorSubject should work, it should be called, whenever LoaderService.status changes. Why not? What am I doing wrong?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
masterfloda
  • 2,908
  • 1
  • 16
  • 27
  • Remove `providers: [LoaderService]` from `LoaderComponent`. – cartant Sep 19 '17 at 22:39
  • @cartant AWESOME! that solved it! Thanks a lot! Can you submit it as an answer, so I can accept it? Also, could you maybe explain why this solved the problem? – masterfloda Sep 19 '17 at 22:56

1 Answers1

3

The problem is due to this line in LoaderComponent:

providers: [LoaderService]

By specifying the service using a component-level provider, you are informing Angular that the component is to receive its own instance of the service. That's not what you want; you want to share the module-level instance.

Just remove that line from LoaderComponent so that a single instance of the service is used.

cartant
  • 57,105
  • 17
  • 163
  • 197