0

I am trying to show a spinner using a observable which resolves to a boolean value using a async pipe in a template. The value of the observable is set in the service. I am not able to figure out whats going wrong. The observable value seems to resolve to undefined. Why this could be not working?

Here is the stackblitz created for the example.

2 Answers 2

1

You should not use a Subject(), or do the setLoader in ngAfterViewInit, at the moment you do .next(true) the template is not subscribed to it yet:

Solution 1:

export class AppComponent {
   loading$: Observable<boolean>;

   constructor(private myservice: MyService) {
     this.loading$ = myservice.loading$;
   }

   ngAfterViewInit() {
     this.myservice.setLoader(true);
   }
}

Solution 2:

export class AppComponent {
   loading$: Observable<boolean>;

   constructor(private myservice: MyService) {
     this.loading$ = myservice.loading$.pipe(
       shareReplay(1)
     );
   }

   ngOnInit() {
     this.myservice.setLoader(true);
   }
}

Solution 3:

private loadingSource = new ReplaySubject(1);

loading$ = this.loadingSource.asObservable();

constructor() { }

setLoader(isLoading: boolean) {
  this.loadingSource.next(isLoading);
}
Sign up to request clarification or add additional context in comments.

4 Comments

Is it necessary for an observable to subscribe it before value is emitted? If yes, then how come this works - const obs = Observable.create(observer => { observer.next(1) }); obs.subscribe( data => { console.log(data) }); Here subscription is done after the value is emitted and it still works.
@DevChoudhary it depends on the Observable, a Subject is a hot observable, A hot Observable is an Observable that can start emitting events before you subscribe. This means you can miss previous events that have already emitted. In your example you create an Observable with a certain subscriber function. This subscriber function is called when you run obs.subscribe, which means you subscribe before the observer.next(1) is called
Okay, that means even if we convert a subject to an observable using asObservable() method, it stills behave like a subject in a hot way. So previous events emitted are lost. The asObservable() method for subject does not make it cold or lazy observable, right?
@DevChoudhary exactly, but if you add a pipe to it with a shareReplay(1) operator, you get what you want. But you basically made a ReplaySubject, so you might as well use that
1

It is undefined because we run setLoader inside ngOnInit where view is not ready. At that time, loading$ in service class is still undefined. When template renders it, definitely it is undefined.

I'd move the setLoader to ngAfterViewInit

import { Component, VERSION, AfterViewInit } from '@angular/core'; // import AfterViewInit
import { MyService } from './my.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit { // add AfterViewInit
 constructor(public myservice: MyService) {
 }

 ngOnInit() {

 }

 ngAfterViewInit() {
   this.myservice.setLoader(true); // move it here
 }
}

Also I checked that there's a typo in the template, the original code uses myservice.loader$ but it is supposed to be myservice.loading$.

<div *ngIf="myservice.loading$ | async">
  Loading
</div>

Hope it helps

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.