html – Add dynamic loading messages with APP_INITIALIZER

Updated Question: HOW COULD I ADD A COMPONENT TO AN APP_INITIALIZER?

I have an app which do some backend calls before initializing. I have a loading screen for this, which tells the user that something is currently loading. The problem is that I also want to show what is currently loading. If one of the backend calls fails, it should specifically display what went wrong. To illustrate the problem, I have programmed a small sample app.

app.module.ts

import { APP_INITIALIZER, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatTreeModule } from '@angular/material/tree';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { TreeNodeComponent } from './tree-node/tree-node.component';
import { ListElementComponent } from './list-element/list-element.component';
import { HttpClient } from '@angular/common/http';
import { forkJoin, switchMap, tap } from 'rxjs';
import { UserService } from './user.service';
import { ProudctService } from './proudct.service';

@NgModule({
  declarations: [
    AppComponent,
    TreeNodeComponent,
    ListElementComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatButtonModule,
    MatMenuModule,
    MatTreeModule,
    MatIconModule,
    MatInputModule
  ],
  providers: [{
    provide: APP_INITIALIZER,
    multi: true,
    deps: [UserService, ProudctService],
    useFactory: getUserConfig
  }],
  bootstrap: [AppComponent]
})
export class AppModule { }




export function getUserConfig(userService: UserService, productService: ProudctService) {
  return function () {
    switchMap(() => userService.getUsers()),
    switchMap(() => {
        const user$ = userService.getUsers();

        const product$ = productService.getAllProducts();

        return forkJoin([user$, product$]);
    })
    return new Promise<void>((resolve, reject) => {
      setTimeout(() => {
        console.log("Async Work Complete");
        resolve();
      }, 7000);
    });
  }
}

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>FocusManagerPlayground</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body class="mat-typography">
  <app-root>
    <div class="splash">Loading User...</div>
    <div class="splash">Loading Product...</div>
  </app-root>
</body>
</html>

and here a screenshot how it should look like: If the first backend call was successful, on the right of the loading message should be a success icon anf if it fails, there should be a fail icon. As the same by loading the products.

EDIT @Brians Solution

app.module.ts

import { APP_INITIALIZER, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatTreeModule } from '@angular/material/tree';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { TreeNodeComponent } from './tree-node/tree-node.component';
import { ListElementComponent } from './list-element/list-element.component';
import { HttpClient } from '@angular/common/http';
import { forkJoin, switchMap, tap } from 'rxjs';
import { UserService } from './user.service';
import { ProudctService } from './proudct.service';
import { LoadingComponent } from './loading/loading.component';

export const userFactory = (provider: LoadingComponent): (() => Promise<void>) => () => provider.load();

@NgModule({
  declarations: [
    AppComponent,
    TreeNodeComponent,
    ListElementComponent,
    LoadingComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatButtonModule,
    MatMenuModule,
    MatTreeModule,
    MatIconModule,
    MatInputModule
  ],
  providers: [{ provide: APP_INITIALIZER, useFactory: userFactory, deps: [UserService,ProudctService], multi: true },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

loading.component.html

<div class="splash">Loading User...</div>
<div class="splash" *ngIf="usersLoading">check icon</div>
<div class="splash" *ngIf="usersLoadError">fail icon</div>

<div class="splash">Loading Product...</div>

loading.component.ts

import { Component, OnInit } from '@angular/core';
import { UserService } from '../user.service';

@Component({
  selector: 'app-loading',
  templateUrl: './loading.component.html',
  styleUrls: ['./loading.component.css']
})
export class LoadingComponent {

  usersLoading!: boolean
  usersLoadError!: boolean

  constructor(private userService: UserService) { }

  load(): Promise<void> {
    this.usersLoading = true;
    return new Promise((resolve, reject) => {
      this.userService.getUsers().subscribe((response) => {
        this.usersLoading = false;
        this.usersLoadError = false;
        resolve();
      },
        (err) => {
          this.usersLoading = false;
          this.usersLoadError = true;
          reject();
        });
    });
  }

}

Leave a Comment