Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular-universal getting error: You must pass in a NgModule or NgModuleFactory to be bootstrapped

I converted my existing angular-cli application to angular-universal by following this guide.

You can look at my complete source code here.

I am able to build both browser and client projects but I get following error when I view the app in the browser:

Error: You must pass in a NgModule or NgModuleFactory to be bootstrapped at View.engine (D:\ng-ssr-demo\dist\server.js:359545:23)

The issue is in my server.ts file where AppServerModuleNgFactory is being undefined and as this factory is used for bootstraping the app in the express backend, the bootstrapping is failing.

./server.ts:

const MockBrowser = require('mock-browser').mocks.MockBrowser; const mock = new MockBrowser();  // Faster server renders w/ Prod mode (dev mode never needed) enableProdMode();  // Express server const app = express();  const PORT = process.env.PORT || 4000; const DIST_FOLDER = join(process.cwd(), 'dist');  // Fix for window error: const domino = require('domino'); const fs = require('fs'); const path = require('path'); const template = fs.readFileSync(path.resolve('./', 'dist', 'browser/', 'index.html')).toString(); const win = domino.createWindow(template);  // workaround for leaflet global['window'] = win; global['document'] = win.document;  // workaround for nex-charts win.screen = { deviceXDPI: 0, logicalXDPI: 0 }; global['MouseEvent'] = win.MouseEvent; global['navigator'] = mock.getNavigator();   // * NOTE :: leave this as require() since this file is built Dynamically from webpack const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');  // AppServerModuleNgFactory is undefined console.log('AppServerModuleNgFactory', AppServerModuleNgFactory);  // This is injected console.log('LAZY_MODULE_MAP', LAZY_MODULE_MAP);  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) app.engine('html', ngExpressEngine({   bootstrap: AppServerModuleNgFactory,   providers: [     provideModuleMap(LAZY_MODULE_MAP)   ] })); 

./webpack.server.config.js:

module.exports = {   entry: {     // This is our Express server for Dynamic universal     server: './server.ts',     // This is an example of Static prerendering (generative)     prerender: './prerender.ts'   },   target: 'node',   resolve: { extensions: ['.ts', '.js'] },   // Make sure we include all node_modules etc   externals: [/node_modules/],   output: { path: path.join(__dirname, 'dist'), filename: '[name].js' },   module: { rules: [{ test: /\.ts$/, loader: 'ts-loader'}] },   plugins: [     new webpack.ContextReplacementPlugin(       // fixes WARNING Critical dependency: the request of a dependency is an expression       /(.+)?angular(\\|\/)core(.+)?/,       path.join(__dirname, 'src'), // location of your src       {} // a map of your routes     ),     new webpack.ContextReplacementPlugin(       // fixes WARNING Critical dependency: the request of a dependency is an expression       /(.+)?express(\\|\/)(.+)?/,       path.join(__dirname, 'src'), {}     )   ] } 

./src/tsconfig.server.json:

{   "extends": "../tsconfig.json",   "compilerOptions": {     "outDir": "../out-tsc/app",     "module": "commonjs",     "baseUrl": "./",     "types": ["node"],     "typeRoots": ["../node_modules/@types"],     "paths": {       "@angular/*": [         "../node_modules/@angular/*"       ],       "@nebular/*": [         "../node_modules/@nebular/*"       ]     }   },   "exclude": [     "test.ts",     "**/*.spec.ts"   ],   "angularCompilerOptions": {     "entryModule": "app/app.server.module#AppServerModule",     "platform": 1   } } 

./src/main.server.ts:

export { AppServerModule } from './app/app.server.module'; 

./src/app/app.module.ts:

@NgModule({   declarations: [AppComponent],   imports: [     BrowserModule.withServerTransition({appId: 'my-app'}),     BrowserAnimationsModule,     HttpModule,     AppRoutingModule,      NgbModule.forRoot(),     ThemeModule.forRoot(),     CoreModule.forRoot(),     environment.production ? ServiceWorkerModule.register('./ngsw-worker.js') : [],   ],   bootstrap: [AppComponent],   providers: [     { provide: APP_BASE_HREF, useValue: '/' }, WebWorkerService,   ], }) export class AppModule { } 

./src/app/app.server.module.ts:

@NgModule({   imports: [     AppModule,     ServerModule,     ModuleMapLoaderModule   ],   bootstrap: [AppComponent], }) export class AppServerModule {} 
like image 235
Saurabh Palatkar Avatar asked Apr 16 '18 05:04

Saurabh Palatkar


People also ask

How SSR works in Angular?

Angular Interview Q & A series Server side Rendering (SSR) is a modern technique to convert a Single Page Application (SPA) running in the browser into a server based application. Usually, in SPA, the server returns a simple index. html file with the reference to the JavaScript based SPA app.

Does Angular support SSR?

Here you can only see , and no static HTML page is displayed; it is because most of the data is loaded at the client-side. By default, the application created with CLI is configured to have client-side rendering. While with the help of Angular Universal, we can configure server-side rendering (SSR) smoothly.

What are the main reasons to use Angular Universal for your app?

Angular Universal executes on the server, generating static application pages that later get bootstrapped on the client. This means that the application generally renders more quickly, giving users a chance to view the application layout before it becomes fully interactive.

What does Angular Universal do?

When we use Angular Universal, we will render the initial HTML and CSS shown to the user ahead of time. We can do it for example at build time, or on-the-fly on the server when the user requests the page. This HTML and CSS will be served initially to the user, so the user can see something on the screen quickly.


1 Answers

ANGULAR 8 UPDATE (v8.0.1 - as of June 2019)

For me, I ran the universal setup using the Angular CLI and it didn't work straight out the box. After hours of reading around it I found that the packages were miss-matched. I had Angular 8 running the project, but nguniversal packages in my package.json were specified at v7.

I would recommend updating these to the same version of angular you have installed. The CLI should really do this by default but I guess not (yet?).

For Angular 8, at the time of writing (June 2019) this is version @next, or @8.0.0-rc.1, so run the following command to update:

npm i --save @nguniversal/express-engine@next @nguniversal/module-map-ngfactory-loader@next 

After I updated this I was still getting the error, and managed to identify another issue. I also had to turn off the Ivy compiler for the server-side application. To do this I added the following line to the tsconfig.server.json:

{     "extends": "./tsconfig.app.json",     ...     "angularCompilerOptions": {         ...         "enableIvy": false     }     ... } 

Ivy is off by default in Angular 8, but because my tsconfig.server.json extends tsconfig.app.json, and the app config had Ivy turned on, I had to explicitly turn it off for the server config.

After all this, server requests for content actually started to work for me.

If this doesn't help you, I would recommend downloading the universal example project mentioned in the angular docs:

Download: https://angular.io/generated/zips/universal/universal.zip Docs: https://angular.io/guide/universal

Once downloaded, compare all the relevant files to make sure you have the same. If you still get errors in your own project, but the example is working, then, try moving your settings files, modules and components etc inside the example project one by one and see what breaks it. This is how I was able to identify that it was my tsconfig.server.json file that broke it.

like image 71
DoubleA Avatar answered Oct 08 '22 02:10

DoubleA