1
votes

My Angular app contains some services, that need to initialize data asyc before showing up the app components (p.e. loading initial data from an API and the i18n via ng-translate). In a central service I want to observe all the async tasks and provide an Observable to inform other components and services that the system is ready (and hide a loading animation).

My first attemt is:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { combineLatest } from 'rxjs/observable/combineLatest';

@Injectable({
  providedIn: 'root',
})
export class LoadingService {

  private tokens: Observable<boolean>[] = [];
  public tokenObservable: Observable<boolean[]>;

  constructor() {
    this.tokenObservable = combineLatest(this.tokens);
  }

  public registerLoadingToken(token: Observable<boolean>): void {
    this.tokens.push(token);
  }
}

All initializing services has to register an Observable in this LoadingService. But I think I get a timing problem. The Plan is:

  • LoadingService provides the Observable, every Component can subscribe
  • all asyc services register their Observable
  • LoadingService fires its Observable, if every async service has fired at least once

But how can i set up the timing correctly?

If I use combineLatest in the constructor, no token is registered.

If I use combineLatest async (like: setTimeout({}, 0)), the public token observable is null.

Do you have an idea for me?

1
Well, what should happen when you call registerLoadingToken in multiple components? How do you know when you want to start listening to the token Observables?martin
Thats exactly the question. My current attempt is to register a separate subject. registerLoadingToken is calles by every initial loaded and injected service. Combine latest is calles in the OnInit of the app.component.Benjamin Ifland
I was actually asking you what you want to happen.martin

1 Answers

0
votes

Ok: this works:

Loading Service:

import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { combineLatest } from 'rxjs/observable/combineLatest';

@Injectable({
  providedIn: 'root',
})
export class LoadingService {

  private tokens: Observable<any>[] = [];
  private internalSubject = new Subject<any>();
  public tokenObservable: Observable<any>;

  constructor() {
    this.tokenObservable = this.internalSubject.asObservable();
  }

  // call in the OnInit of app.component.ts
  public listenToTokens(): void {
    combineLatest(this.tokens).subscribe(_ => this.internalSubject.next(true));
  }

  public registerLoadingToken(token: Observable<any>): void {
    this.tokens.push(token);
  }
}

My async service:

public awaiter: Subject<any>;

  constructor(
    apiService: ApiService,
    private navigationService: NavigationService,
    private translationService: TranslateService,
    loadingService: LoadingService) {
    loadingService.registerLoadingToken(this.awaiter);
    apiService.get<api.Field[]>('FormMetadata').subscribe(_ => {
      this.setMetadata(_);
      this.awaiter.next(true);
    }, _ => this.handleError(_));
  }

And the appComponent.ts:

  ngOnInit(): void {
    this.loadingService.listenToTokens();
  }

Then I can subscribe this on my MainComponent:

  constructor(loadingService: LoadingService) {
    loadingService.tokenObservable.subscribe(_ => this.state = 'none');
  }