Ensure that the HTTP GET request completes before initializing Angular using the ngOnInit lifecycle hook

Overview

I am working on an Angular application that consists of three components: root, child1 (tabs.component.ts), and child2 (io.component.ts). Additionally, there is a service in place that communicates with a Tomcat server by sending get and post requests.

In the child1 component, I have an ngOnInit method where I call the service method get. Similarly, in child2, there is also an ngOnInit method. The ngOnInit from child1 gets executed first. However, in child1, the get request for fetching data from the server is slow, causing the ngOnInit from child2 to start before

get.subscribe(data=>this.data=data)
.

The issue arises when the ngOnInit method from child2 tries to use the data variable before the get request has returned any data, resulting in it being filled with undefined. The sequence of actions is as follows:

Attempts Made

I have tried using async, await, and toPromise(), but none of them worked as the ngOnInit from child2 always loaded before the get request was completed.

Code Samples

ngOnInit from child1

subsystems: Subsystem[]; //need to ngFor them on html
currentTabId;

constructor(private terminalService: TerminalService) {}

ngOnInit() {
try {
  console.log('child1 ngoninit');
  this.terminalService.getSubsystems().subscribe((data: []) => {
    console.log('get in child1 finished');
    this.subsystems=data;
    this.currentTabId=0;
    this.terminalService.setSubsystem(this.subsystems[this.currentTabId]);
  });
} catch (exception) {
  console.log(exception);
}
}

ngOnInit from child2

Here, I encountered an error:

TypeError: Cannot read property 'name' of undefined

constructor(private terminalService: TerminalService) {}

ngOnInit() {
try {
  console.log('child2 ngoninit');
  this.terminalService
    .getResultsBySubsystem(this.terminalService.getSubsystem().name) //here
    .subscribe((data: Command[])=> {
      console.log(data);
      data.forEach((value)=> {
        this.terminalService.setCurrentResult(value.getCommand+'\n'+value.getResult+'\n');
      });
    });
}
catch (exception) {
  console.log(exception);
  this.terminalService.addCurrentResult(this.CONNECTION_ERROR_MSG);
}
}

terminal.service.ts

subsystem: Subsystem;

constructor(private httpClient: HttpClient) {}

getSubsystems() {
  return this.httpClient.get('http://localhost:8080/subsystems');
}

getResultsBySubsystem(name: string) {
  return this.httpClient.get('http://localhost:8080/subsystems/'+name+'/result');
}

getSubsystem() {
  console.log('getSubsystem terminal.service invoking');
  return this.subsystem;
}

setSubsystem(subsystem: Subsystem) {
  console.log('setSubsystem terminal.service invoking ');
  this.subsystem=subsystem;
}

How can I ensure that the get request is completed before the ngOnInit from child2 attempts to access the variable name from the subsystem?

Update

Thank you for your suggestions. I tested using Resolve, but encountered

It seems that the resolve function is called after the get request, however, this.actr.data, which should trigger the resolve, does not work as expected. Quite confusing.

New implementation of getSubsystems in terminal.service

import {map} from 'rxjs/operators';


subsystem: Subsystem;

constructor(private httpClient: HttpClient) {}

getSubsystems() {
    console.log('getSubsystems in terminal.service invoking');
    return this.httpClient.get<Subsystem[]>('http://localhost:8080/subsystems')
      .pipe(map(value=>{console.log(value); return value;}));
  }

child1

subsystems: Subsystem[];
currentTabId;

constructor(private terminalService: TerminalService, private actr: ActivatedRoute) {}

ngOnInit() {
    console.log('child1 ngoninit');
    try {
      this.terminalService.setCurrentResult('Connecting...');
      this.actr.data.subscribe((data: []) => { //this
        console.log('get in child1 finished');
        this.subsystems=data;
        console.log(data);
        this.currentTabId=0;
        this.terminalService.setSubsystem(this.subsystems[this.currentTabId]);
      });
    } catch (exception) {
      console.log(exception);
    }
  }

resolve.service

export class ResolverService implements Resolve<any>{

  constructor(private terminalService: TerminalService) { }

  resolve(){
    console.log('resolve');
    return this.terminalService.getSubsystems();
  }
}

resolve.module

import {RouterModule, Routes} from '@angular/router';
import {ResolverService} from './services/resolver.service';

const routes: Routes = [
  {
    path: '',
    component: AppComponent,
    resolve: {
      subsystems: ResolverService
    }
  }
];

export const routing = RouterModule.forRoot(routes);

@NgModule({
  declarations: [],
  imports: [CommonModule],
  exports: [RouterModule],
  providers: [ResolverService]
})
export class ResolverModule { }

app.module

import {ResolverModule} from './resolver.module';
import { routing } from './resolver.module';
import {RouterModule} from '@angular/router';

@NgModule({
  declarations: [
    AppComponent,
    TabsComponent,
    IoComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    BrowserAnimationsModule,
    HttpClientModule,
    routing
  ],
  exports: [RouterModule],
  providers: [ResolverModule],
  bootstrap: [AppComponent]
})
export class AppModule { }

What could be potentially wrong with this setup?

Answer №1

I found the solution by utilizing the APP_INITIALIZER function

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import {APP_INITIALIZER, NgModule} from '@angular/core';
import { AppComponent } from './app.component';
import {FormsModule} from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TabsComponent } from './tabs/tabs.component';
import {HttpClientModule} from '@angular/common/http';
import { IoComponent } from './io/io.component';
import {AppConfig} from './config/app.config';

export function loadConfiguration(config: AppConfig) {
  return () => config.load();
}

@NgModule({
  declarations: [
    AppComponent,
    TabsComponent,
    IoComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    BrowserAnimationsModule,
    HttpClientModule,
  ],
  bootstrap: [AppComponent],
  providers: [
    AppConfig,
    {provide: APP_INITIALIZER, useFactory: loadConfiguration, deps: [AppConfig], multi: true}
  ]
})
export class AppModule {
}

Create a configuration file and define the load() function in order to fetch data via get request before the constructors of components are initialized

app.config.ts

import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Subsystem} from '../data/subsystem';
import {map} from 'rxjs/operators';

@Injectable()
export class AppConfig {

  private subsystems: Subsystem[] = null;
  private subsystem: Subsystem = null;

  constructor(private http: HttpClient) {

  }

  load() {
    return new Promise((resolve) => {
      this.http
        .get('http://localhost:8080/subsystems')
        .pipe(map(value=>value))
        .subscribe((data: Subsystem[]) => {
          this.subsystems = data;
          resolve(true);
        });
    });
  }

  public getSubsystem() {
    return this.subsystem;
  }

  public setSubsystem(subsystem: Subsystem) {
    this.subsystem=subsystem;
  }

  public getSubsystems() {
    return this.subsystems;
  }

  public setSubsystems(subsystems: Subsystem[]) {
    this.subsystems=subsystems;
  }
}

Utilize the initialized data

tabs.component.ts

import {Component, OnInit} from '@angular/core';
import {Subsystem} from '../data/subsystem';
import {AppConfig} from '../config/app.config';

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

export class TabsComponent implements OnInit {

  subsystems: Subsystem[];
  subsystem: Subsystem;
  currentTabId;

  constructor(private config: AppConfig) {
  }

  ngOnInit() {
    this.currentTabId=0;
    this.subsystems=this.config.getSubsystems();
    this.config.setSubsystem(this.subsystems[this.currentTabId]);
    this.subsystem=this.config.getSubsystem();
  }

  tabPressed(id) {
    this.currentTabId=id;
    this.config.setSubsystem(this.subsystems[id]);
    this.subsystem=this.config.getSubsystem();
  }

}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to Activate Click Event Function Based on Conditions in Angular 2

I am currently working on an Angular 2 project that includes a table with checkboxes in one of the columns. If a user clicks on a checkbox, a modal will pop up displaying more information about that specific row. The catch is, I only want the modal to appe ...

Store the ajax call response in a dynamic function

Currently, I am in the process of creating a series of user-friendly formulas, similar to those found in Excel, for a form builder website. One of the features I have developed is a Lookup function that allows users to specify the value they want and the v ...

insert a new DOM element into an array using jQuery

Looking at the code snippet below: a_ajouter = $('.question'); hidden_div.push(a_ajouter); console.log(hidden_div); Upon examining the output in the console, instead of a DOM object being added to the div as intended, it shows &apo ...

Creating 3D Impostors with spherical shapes in three.js

I am currently in the process of transitioning a native OpenGL application to WebGL using the three.js framework. One challenge I am encountering is implementing spherical impostors. Spherical Impostors consist of quads (or two triangles) that always face ...

What is the best way to create a sign-up box that appears on the same page when you click on the sign-up button

I am interested in implementing a 'sign up' box overlay at the center of my index page for new users who do not have an account. I would like them to be able to easily sign up by clicking on the 'sign up' button. Once they complete the ...

Changing Angular templates and styles dynamically based on different conditions

I have a header component structured as follows: app-header (folder) classic (folder) app-header.component.html app-header.component.scss elegant (folder) app-header.component.html app-header.component.scss ...

Switching Between HTML Using Javascript

I am currently working on an app that allows users to easily check the local weather and temperature in either celsius or fahrenheit. However, I've encountered a problem when trying to switch between the two unit types by simply clicking on the temper ...

What is the best way to find the common elements in two datasets using Nuxt?

I attempted to retrieve data from an API using parameters that are passed through an argument in a v-for loop. In the findUser function, I am able to successfully log the desired data. However, I am unable to retrieve it at the end of the findUser function ...

Warning: The DataTables table with the ID of [tablename] has encountered a login failure for the user with the username 'username'

Currently, I am utilizing the MVC framework /ASP.net and have incorporated datatables into my webpage. However, upon publishing the webpage to our server, I encounter an error where the datatable fails to function properly. Strangely enough, the datatable ...

The TypeScript library React-Time-Ago seems to require a number, but I'm passing it a string instead. I'm struggling to find a way to make it accept a number

import ReactTimeAgo from "react-time-ago" <ReactTimeAgo date = {tweet._createdAt} /> _createdAt displays time in the format 2022-06-02T01:16:40Z To convert this into a more human-readable form like " ...

The function User.find does not exist and it is not possible to replace the `users` model after it has

Currently, I am experimenting with using mongoose, mongoDB, next, and express in a test project. Despite referencing solutions like Cannot overwrite model once compiled Mongoose and others, I am encountering issues unique to my situation. Upon initializat ...

Information will only be displayed after the button has been double-clicked

After a month of experimenting with Angular and being relatively new to web development, I've encountered an issue that I hope someone can help me with. In my simple Angular application, I have a button called roomSearch() which sends form data to an ...

Display a dropdown menu when hovering over with a delay

I recently created a basic navigation menu with dropdown functionality using CSS3 initially, but I decided to enhance it by incorporating jQuery to display the dropdown after a set timeframe. However, I am facing an issue where all dropdowns appear when ho ...

Using pure JavaScript to trigger a pop-up window upon submitting a form

Struggling with sending form data to a PHP page using radio buttons for poll results display. No luck with passing variables using $_POST or $_GET methods. I've checked both, but still nothing. When I tried printing the arrays on the PHP page: <? ...

Leverage the power of Component Router in conjunction with the Upgrade

While attempting to integrate the Angular 2 Component Router with the upgrade adapter in Angular 2 RC 4, I encountered the following error: Bootstrap at least one component before injecting Router. at setupRouter Below is my main.ts file: angular.mo ...

Clicking on the button will advance to the next tab rather than selecting the tab

I have this code snippet in HTML: $(document).ready(function() { $("div.bhoechie-tab-menu>div.list-group>a").click(function(e) { e.preventDefault(); $(this).siblings('a.active').removeClass("active"); $(th ...

Tips for streaming AWS Lambda response in nodeJS

I have a serverless AWS Lambda function that I need to trigger from my Node.js application and stream the response back to the client. Despite searching through the official documentation, I cannot find a straightforward way to achieve this. I am hoping to ...

Combine identical items in a JavaScript array based on how often they appear

I have an array in JavaScript that looks like this: [ { quantity: 1, name: 'Menu sandwiches' }, { quantity: 1, name: 'Menu sandwiches' }, { quantity: 1, name: 'Menu sandwiches' }, { quantity: 1, name: 'Pizza' ...

Obtain the chosen item from a Bootstrap4 dropdown menu by utilizing a button

I am struggling to get a button with dropdown working in Bootstrap4. Below is the HTML code: <div class="row" id="dropdown-box"> <div class="col-lg-6"> <div class="input-group"> <div class="input-group-btn" id="button-grou ...