Search code examples
angularangular-router

Angular 5 Build routes from API data at startup


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?


Solution

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