How to conditional bootstrap angular app?

4k Views Asked by At

I am trying to bootstrap my angular 5 app based on conditions. Basically I have two modules MobileModule and WebModule which contains UI components of web and mobile separately.

I am trying to bootstrap MobileModule if user has opened the app in mobile browser otherwise WebModule.

Here is my main.ts source.

import { enableProdMode }         from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { environment }    from './environments/environment';

import { AppSettings }    from './app/core/app-settings';
import { MobileModule }   from './app/mobile/mobile.module';
import { WebModule }      from './app/web/web.module';


if (environment.production) {
    enableProdMode();
}


/*
 * Bootstrap mobile or web module based on mobile
 * or desktop client.
 */
if (AppSettings.isMobileDevice) {

  platformBrowserDynamic().bootstrapModule(MobileModule)
      .catch(err => console.log(err));
} else {

  platformBrowserDynamic().bootstrapModule(WebModule)
      .catch(err => console.log(err));
}

web.module.ts

import { BrowserModule }  from '@angular/platform-browser';
import { NgModule }       from '@angular/core';

import { AppRoutingModule } from './app-routing/app-routing.module';
import { CoreModule }       from '../core/core.module';
import { SharedModule }     from '../shared/shared.module';
import { AuthModule }       from './auth/auth.module';

import { SignupComponent }  from './auth/signup/signup.component';


@NgModule({
  imports: [
    BrowserModule,        // Support for common angular directives and services
    AppRoutingModule,     // Web routing controller module.
    CoreModule,           // Core modules
    SharedModule,         // Shared modules
    AuthModule,           // App authentication module (signup and login)
  ],
  declarations: [],
  providers: [],
  bootstrap: [SignupComponent]    // Bootstrap Signup component
})
export class WebModule { }

mobile.module.ts

import { BrowserModule }  from '@angular/platform-browser';
import { NgModule }       from '@angular/core';

import { AppRoutingModule } from './app-routing/app-routing.module';
import { CoreModule }       from '../core/core.module';
import { SharedModule }     from '../shared/shared.module';
import { AuthModule }       from './auth/auth.module';

import { SignupComponent }  from './auth/signup/signup.component';


@NgModule({
  imports: [
    BrowserModule,        // Support for common angular directives and services
    AppRoutingModule,     // Mobile app routing controller module
    CoreModule,           // Core modules
    SharedModule,         // Shared modules
    AuthModule,           // App authentication module (signup and login)
  ],
  declarations: [],
  providers: [],
  bootstrap: [SignupComponent]    // Bootstrap Signup component
})
export class MobileModule { }

The above approach is working fine with ng serve but when I try to run it using ng serve --aot option its throwing error in chrome console.

Uncaught Error: No NgModule metadata found for 'WebModule'.  
    at NgModuleResolver.resolve (compiler.js:20242)  
    at CompileMetadataResolver.getNgModuleMetadata (compiler.js:15195)  
    at JitCompiler._loadModules (compiler.js:34405)  
    at JitCompiler._compileModuleAndComponents (compiler.js:34366)  
    at JitCompiler.compileModuleAsync (compiler.js:34260)  
    at CompilerImpl.compileModuleAsync (platform-browser-dynamic.js:239)  
    at PlatformRef.bootstrapModule (core.js:5567)  
    at eval (main.ts:26)  
    at Object../src/main.ts (main.bundle.js:53)  
    at __webpack_require__ (inline.bundle.js:55)  

My project structure is

|-node_modules
|-src
   |-app
      |-core
      |-mobile
         |-mobile.module.ts
      |-shared
      |-web
         |-web.module.ts

I tried several things to conditionally bootstrap the app but no success. Any suggestion will be appreciated.

3

There are 3 best solutions below

0
On BEST ANSWER

After a lot research I found that we can only bootstrap single module at a time. Which means single root module per angular app.

However, since the time of angular lazy loading of modules we can load all feature modules dynamically. Hence, the idea is to load one root module and based on conditions you can load any sub-module dynamically.

Let's consider above example.

import { enableProdMode }         from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { environment }    from './environments/environment';
import { AppModule }      from './app/app.module';


if (environment.production) {
    enableProdMode();
}


/*
 * Bootstrap single root module
 */
platformBrowserDynamic().bootstrapModule(AppModule)
      .catch(err => console.log(err));

app.module.ts

import { BrowserModule }  from '@angular/platform-browser';
import { NgModule }       from '@angular/core';
import { RouterModule, Routes }     from '@angular/router';

import { CoreModule }       from '../core/core.module';
import { SharedModule }     from '../shared/shared.module';

import { AppComponent }  from './app.component';


const isMobileDevice = true; // Mobile device condition    


const routes: Routes = [
  {
    path        : 'app',
    loadChildren: isMobileDevice ? './mobile/mobile.module#MobileModule' : './web/web.module#WebModule'    // Conditionally load mobile or web module
  }
];


@NgModule({
  imports: [
    BrowserModule,        // Support for common angular directives and services
    RouterModule.forRoot(routes),
    CoreModule,           // Core modules
  ],
  declarations: [],
  providers: [],
  bootstrap: [AppComponent]    // Bootstrap Signup component
})
export class AppModule { }

Hope this helps to some other folks who wanted such behaviour in their angular app.

0
On

I achieved the conditionally loading of a module in main.ts by using fileReplacements in the angular.json file by creating an object under configuration and in that, I added replacements as below:

"fileReplacements": [
    {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.lib.ts"
    },
    {
        "replace": "src/main.ts",
        "with": "src/main.lib.ts"
    }
]

The above solved my problem.

Hope this helps someone.

Thanks...

0
On

I think Angular 5 does not support multiple root modules, because the way AOT (Ahead of Time compilation) currently works. It seems it only compiles the first module and its dependencies.

A workaround is to have a root module only for bootstrapping, and use Router with Guards, and lazy-loading only the desired module inside the root module.

References: