tohhant
tohhant

Reputation: 127

Wait for http get request is finished before ngoninit starts angular

Summary

I have an application on Angular. There are i have three components: root, child1 (tabs.component.ts) and child2 (io.component.ts). Also, i have a service that sends get and post requests to tomcat server.

In child1 i have a ngoninit, where i invoke service method get. Also i have ngoninit in child2. Ngoninit from child1 starts first. In child1 get request fetch data from server too slow and ngoninit from child2 starts before get.subscribe(data=>this.data=data).

So the problem is that ngoninit method from child2 uses this data and as get request not returned data yet it fills with undefined. So, i have next sequence:

sequence (console)

Tried

to use async, await, toPromise() but it doesn't work, ngoninit from child2 in all cases loaded before get finished.

Some code

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've got 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 to wait for get request before ngoninit from child2 will call variable name from subsystem?

Update

thx for your answers. I've tried Resolve, but have

newConsole

as seen, resolve called after get, though this.actr.data, as i understand, must invoke resolve. Confused.

new getSubsystems from 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's wrong with this?

Upvotes: -1

Views: 2201

Answers (1)

tohhant
tohhant

Reputation: 127

I solved it using APP_INITIALIZER

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 loadConfig(config: AppConfig) {
  return () => config.load();
}

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

create config file and load() function there to load get request before components' constructors will be loaded

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;
  }
}

use 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();
  }

}

Upvotes: 3

Related Questions