2

I need to create routes at Angular application startup based on the data received from API (site map). 1. APP_INITIALIZER calls SettingsService.loadSettings to get the data from API 2. Data comes to ROUTES and desired routes are built.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule, ROUTES } from '@angular/router';

import { AppComponent } from './app.component';
import { MuseumComponent } from './museum/museum.component';
import { SettingsService } from './services/settings.service';

export function initSettings(settings: SettingsService) {
  return () => settings.loadSettings();
}

export function buildRoutes(settings: SettingsService) {
  console.log('SettingsService.currentSettings', settings.currentSettings);
  const routes = [];
  settings.currentSettings.site_map.forEach(element => {
   routes.push({
     path: `/${element.url}`,
     component: MuseumComponent
   });
  });
  routes.push({
   path: '**',
   component: AppComponent
  });
  console.log(routes);
  return routes;
}

@NgModule({
  declarations: [
    AppComponent,
    MuseumComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    RouterModule.forRoot([])
  ],
  providers: [
    SettingsService,
    {
      'provide': APP_INITIALIZER,
      'useFactory': initSettings,
      'deps': [SettingsService],
      'multi': true
    },
    {
      'provide': ROUTES,
      'multi': true,
      'useFactory': buildRoutes,
      'deps': [SettingsService]},
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

settings.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

import { Config } from './../config';

@Injectable()
export class SettingsService {

  private apiUrl = Config.apiUrl;

  public currentSettings;

  constructor(
    private http: HttpClient
  ) { }

  loadSettings(): Promise<any> {
    return new Promise ((resolve, reject) => {
      const url = this.apiUrl + `/settings`;
      this.http.get(url)
      .subscribe(
        response => {
          console.log('API answer: ', response);
          this.currentSettings = response;
          resolve(true);
        },
        err => {
          console.log('Server error: ' + JSON.stringify(err));
          reject(false);
        }
      );
    });
  }
}

I receive an error on console:

SettingsService.currentSettings undefined
main.ts:12 TypeError: Cannot read property 'site_map' of undefined

If I make this:

app.module.ts

export function buildRoutes(settings: SettingsService) {
  console.log('SettingsService.currentSettings', settings.currentSettings);
  const routes = [];
  /*settings.currentSettings.site_map.forEach(element => {
   routes.push({
     path: `/${element.url}`,
     component: MuseumComponent
   });
  });*/
  routes.push({
   path: '**',
   component: AppComponent
  });
  console.log(routes);
  return routes;
}

I receive:

SettingsService.currentSettings undefined
app.module.ts:27 [{…}]
0: {path: "**", component: ƒ} length: 1 __proto__: Array(0)
settings.service.ts:23 API answer:  {languages: Array(2), entity_types: 
Array(5), site_map: Array(2)}
core.js:3688 Angular is running in the development mode. Call 
enableProdMode() to enable the production mode.

Look's like, ROUTES is happened before APP_INITIALIZER. Why it is?

1 Answer 1

5

I used wrong approach, solution was founded here Angular. Router DI not working when using APP_INITIALIZER

Right code:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { MuseumComponent } from './museum/museum.component';
import { SettingsService } from './services/settings.service';
import { ApiService } from './services/api.service';

export function initSettings(settings: SettingsService) {
 return () => settings.loadSettings();
}

@NgModule({
 declarations: [
  AppComponent,
  MuseumComponent
 ],
 imports: [
  BrowserModule,
  HttpClientModule,
  RouterModule.forRoot([])
 ],
 entryComponents: [
  MuseumComponent
 ],
 providers: [
  SettingsService,
  ApiService,
  {
   'provide': APP_INITIALIZER,
   'useFactory': initSettings,
   'deps': [SettingsService],
   'multi': true,
  }
],
bootstrap: [AppComponent]
})
export class AppModule { }

settings.service.ts

import { Injectable, Injector } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Router } from '@angular/router';

import { ApiService } from './api.service';

import { MuseumComponent } from '../museum/museum.component';

@Injectable()
export class SettingsService {

 currentSettings: any;

 constructor(
  private injector: Injector,
  private api: ApiService
 ) {   }

loadSettings(): Promise<any> {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
      const router = this.injector.get(Router);
      console.log(router);
      this.api.getSettings()
      .subscribe(
        response => {
          this.currentSettings = response;
          this.currentSettings.site_map.forEach(element => {
            router.config.push({ path: `${element.url}`, component: MuseumComponent });
          });
          resolve(true);
        },
        err => {
          console.log(err);
          reject(false);
        }
      );
   });
  });
 }
}

api.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

import { Config } from './../config';

@Injectable()
export class ApiService {

private apiUrl = Config.apiUrl;

constructor(
 private http: HttpClient
) { }

 public getSettings(): Observable<any> {
  const url = this.apiUrl + `/settings`;
  return this.http.get(url);
 }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.