0

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 displayed 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(() => {
        const user$ = userService.getUsers();

        const product$ = productService.getAllProducts();

        return forkJoin([user$, product$]);
    })
  }
}

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 *ngIf="loading">Succesfull</div><div *ngIf="!loading">failed</div>
    <div class="splash">Loading Product...</div>
  </app-root>
  <app-loading></app-loading>
</body>
</html>

and here a screenshot how it should looks 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. enter image description here

Klaus M.
  • 170
  • 11
  • Please don't cut and paste and answer into your question. If the answer helped, upvote/accept it. If you have a different question, then ask a different question – Liam May 13 '22 at 12:27
  • @Liam The answer doesent help me because, everthing of this I know. Also I haven't ask a different question, only clarify it ! – Klaus M. May 13 '22 at 12:38
  • My question is, how could I implement this example which one you could see in the screenshot. One idea was that I could outsourcing the code in a seperate component. But how could I use a component in the index.html file before app initialization. I hope now my issue was clarified. – Klaus M. May 13 '22 at 12:42
  • That seems exactly what the answer below is doing – Liam May 13 '22 at 13:16
  • I think you just need to read up on component more https://angular.io/guide/component-overview. Adding a component to a view is as simple as `` – Liam May 13 '22 at 13:17
  • @Liam either you haven't understand my question or I can't explain it for you. I know how could I add components. But the tricky thing is here, to add them before app initialization. Just using `````` in the index file is not working. Also after adding this in ```[bootstrap] ```. In this case the component is showing after app-initialization. But again this is not my goal! – Klaus M. May 13 '22 at 13:39
  • What does "before app initialization" mean? That's not a thing. Your web page loads, the html, css and JS are downloaded. It runs the Javascript. Which bit is "app initialization" to you? You obviously can't run any javascript until the end of this process. Because that's not how the internet works. – Liam May 13 '22 at 13:44
  • Your Js code makes zero sense. Why are you loading the users twice and what's the set timeout for? I suspect this is a dupe of [How to return the response from an asynchronous call](https://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call). **Again**, the below answer (lets ignore the dubious use of a promise) seems to actually do what you want it to do. I just don't think your understanding how async works. – Liam May 13 '22 at 13:46
  • @Liam I know that this example doesn't make sense. It should simulate my issue. That is a task of an example ! I can't post my real code. Before app initialization is meaning, that I have to use the loading Component before the angular app was initialized with APP_INITIALIZER. I have an loading screen by first opening the app, which loads some user data and other observables. The promise should simulate the loading time of this dependecies. In my loading screen are actually a loading spinner which one is showing before app initialization. – Klaus M. May 13 '22 at 14:20
  • @Liam it would be helpful if you could write your ideas as a answer or optimal as a working code snipped – Klaus M. May 13 '22 at 14:25
  • I can't do that because your question doesn't make any sense – Liam May 13 '22 at 14:39
  • @Liam maybe you can't do that because you just don't understand the issue, but no worries – Klaus M. May 13 '22 at 14:47

1 Answers1

-2

Not sure if your example is what your doing or just meant to illustrate but is there a reason your not having the loading messages in the component? I would put your messages in a component and then separate the calls into a service that is kicked off on initialization. The component can reference the service and then you can use something like flags to update the template.

app.module.ts

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

@NgModule({
    providers: [
        UserService,
        { provide: APP_INITIALIZER, useFactory: userFactory, deps: [UserService], multi: true },

Example Service -- just doing the UserService as an example

@Injectable()
export class UserService {
 usersLoading: boolean
 usersLoadError: boolean
 user: any;
  load(): void {
    this.usersLoading = true;
    this.getData();
  }

  getData() {
    setTimeout(() => {
      this.user = { id: 1, name: 'hi' };
      this.usersLoading = false;
      this.usersLoadError = false;
    }, 5000);
  }
}

Template -- I don't really like flags but just using here to make a point you could also just check that the user property is truthy.


@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>
    {{userService.usersLoading ? 'Loading' : 'loaded' }}`,
  styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent {
  @Input() name: string;

  constructor(public userService: UserService) {}
}


Please find a working example here:https://stackblitz.com/edit/angular-tj57g2?file=src/app/user.service.ts

Brian
  • 179
  • 7
  • I havent access to a userService variable in my index.html. Therefore your example isnt working – Klaus M. May 12 '22 at 13:28
  • could you please add a working code snipped ? – Klaus M. May 12 '22 at 13:35
  • and ngif isn't working in the index file. – Klaus M. May 12 '22 at 14:00
  • Sorry if the snippet is not clear I can edit but my suggestion is to put the loading message in a component so you can access your services. The intro to this answers tries to lay this out but I can try to outline better. Basically you can kick off your api calls in the background with APP_INITIALIZER then let Angular load your "LandingComponent" which can have a spinner or a message. – Brian May 12 '22 at 15:51
  • its very difficult to understand without any visual examples, but have I understand it rigth ? I have to outsource the code with the loading messages in a seperate component like this ? app.module.ts ```export const userFactory = (provider: LoadingComponent): (() => Promise) => () => provider.load(); ``` and add it to the providers ```providers: [{ provide: APP_INITIALIZER, useFactory: userFactory, deps: [UserService,ProudctService], multi: true }, ]``` – Klaus M. May 13 '22 at 08:36
  • I have edit now the question on top with your solution. I am not sure if I uderstand it right, but I dont know how I could use the loading component as a loading screen. Thank you for the help – Klaus M. May 13 '22 at 08:47
  • The promise here is pointless. – Liam May 13 '22 at 13:18
  • your right about the promise don't why I have that in mine. I updated to remove the promise and added a stackblitz – Brian May 13 '22 at 17:33