Angular 7 Universal, ReferenceError: window is not defined
Asked Answered
T

4

4

I'm making a SSR with Angular 7 and Angular Universal, but when I use Fusioncharts from third parties I've got this errors when i run this script npm run build:ssr && npm run serve:ssr

ReferenceError: window is not defined
    at Object.undefined.module.exports.module.exports (C:\Users\ridho.fauzan\Documents\angular-app\bizhare-frontend\desktop\dist\server.js:161938:807)

I've tried to use domino on the server.ts files but it still not working

this is my server.ts file

import 'zone.js/dist/zone-node';
import {enableProdMode} from '@angular/core';
// Express Engine
import {ngExpressEngine} from '@nguniversal/express-engine';
// Import module map for lazy loading
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';

import * as express from 'express';
import {join} from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 8086;
const DIST_FOLDER = join(process.cwd(), 'dist/browser');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main');

// 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)
  ]
}));

app.set('view engine', 'html');
app.set('views', DIST_FOLDER);

// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
app.get('*.*', express.static(DIST_FOLDER, {
  maxAge: '1y'
}));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render('index', { req });
});

// Start up the Node server
app.listen(PORT, () => {
  console.log(`Node Express server listening on http://localhost:${PORT}`);
});

Towill answered 7/7, 2019 at 12:31 Comment(2)
refer to this post - #46850335Horology
Can you post your code when you tried domino?Lave
C
4

In angular 9 ssr nguniversal this is how I defined window in server.ts and it worked for me.


//server.ts

import 'zone.js/dist/zone-node';
import {enableProdMode} from '@angular/core';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';

enableProdMode();

// The Express app is exported so that it can be used by serverless Functions.
export function app() {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
  const domino = require('domino');
  const win = domino.createWindow(indexHtml);
  // mock
  global['window'] = win;
  global['document'] = win.document;
  global['navigator'] = win.navigator;
  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // Example Express Rest API endpoints
 // Serve static files from /browser
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Universal engine
  server.get('*', (req, res) => {
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  });

  return server;
}

function run() {
  const port = process.env.PORT || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './src/main.server';

I don't know where the domino module comes from b/c I didn't install it but it looks like it comes from angular cli when you set up nguniversal 🤷‍♂️

Cardon answered 30/4, 2020 at 21:29 Comment(0)
L
0

The error you mentionned should be solved by using domino if you have no other choice

const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();
const domino = require('domino');
const win = domino.createWindow(template);
global['window'] = win;
global['document'] = win.document;
global['navigator'] = win.navigator;

If your error only occurs at runtime, you could try not initialising the charts if your app is running server side, e.g.

import { PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

export class YourComponent implements OnInit {
  constructor(@Inject(PLATFORM_ID) private platform: Object) { }
  ngOnInit() {
      if (isPlatformBrowser(this.platform)) {
        //Initialise your charets here
      }

  }
}
Lave answered 8/7, 2019 at 14:9 Comment(2)
where should i put the domino script exactly? is it before the const {AppServerModuleNgFactory, LAZY_MODULE_MAP}?Towill
You only to do it once, so yesLave
W
0

For instance, often invocations of the global window element are to get window size, or some other visual aspect. However, on the server, there is no concept of "screen", and so this functionality is rarely needed.

This can be accomplished using Angular's Dependency Injection (DI) in order to remove the offending code and drop in a replacement at runtime. Here's an example:

// window-service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class WindowService {
  getWidth(): number {
    return window.innerWidth;
  }
}
// server-window.service.ts
import { Injectable } from '@angular/core';
import { WindowService } from './window.service';

@Injectable()
export class ServerWindowService extends WindowService {
  getWidth(): number {
    return 0;
  }
}
// app-server.module.ts
import {NgModule} from '@angular/core';
import {WindowService} from './window.service';
import {ServerWindowService} from './server-window.service';

@NgModule({
  providers: [{
    provide: WindowService,
    useClass: ServerWindowService,
  }]
})

Hope this would help you.

Wurth answered 13/10, 2023 at 11:10 Comment(0)
C
0

Im using angular 16 the solution I found and from various sources.

first run if you have not already:

npm i --save domino

also including with the answer localStorage issue answer:

npm i --save localstorage-polyfill

in your server.ts file

import 'localstorage-polyfill';
import domino from 'domino;
const template = fs.readFileSync(join('dist/your project name/browser', 'index.html'))
                   .toString();
const window: Window = domino.createWindow(template);
global.window = window as (Window & typeof globalThis);
global.document = window.document;
global.navigator = window.navigator;
global.localStorage = localStorage;

if you get an error:

Type 'Window' is not assignable to type 'Window & typeof globalThis'.

when setting window make sure to add:

 as (Window & typeof globalThis)

if after running build:ssr and then running serve:ssr you get an error:

TypeError: Right-hand side of 'instanceof' is not an object

Error is domino related and angular 17 was fixed. I have not found a solution for the domino error yet. Will update if found.

Cancer answered 12/4 at 17:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.