7

I am working with the library ng2-mqtt and I used it im my component like this:

 import 'ng2-mqtt/mqttws31.js';
declare var Paho: any;

Now I am getting following error:

ReferenceError: window is not defined
    at Object.<anonymous> (/Users/Picchu/Documents/em3/node_modules/ng2-mqtt/mqttws31.js:2143:4)
    at Module._compile (module.js:556:32)
    at Object.Module._extensions..js (module.js:565:10)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load (module.js:424:3)
    at Module.require (module.js:483:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/Picchu/Documents/em3/dist/server.js:18707:18)

How can I fix this issue?

mogli mogli
  • 263
  • 1
  • 5
  • 15
  • 2
    You can run this part of the code selectively only in the browser by using isPlatformBrowser available in angular/common – Ahmed Shabib Oct 27 '17 at 04:03

5 Answers5

9

UPDATE

Extending Leon Li's answer, we can avoid loading components that cannot be rendered on server side if it requires Browser APIs like location or window.

This answer explains how to use

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

constructor( @Inject(PLATFORM_ID) platformId: Object) {
  this.isBrowser = isPlatformBrowser(platformId);
}

Just inject PLATFORM_ID into your service, and pass it to isPlatformBrowser or isPlatformServerto get a Boolean value. Accordingly you can show/hide the components that cannot be rendered on server if they depend on Browser APIs.

Mahesh
  • 3,727
  • 1
  • 39
  • 49
8

One possible way to avoid server error is not to render the component(if it is an option) that uses window. Something like:

<ng-container *ngIf="isBrowser">
   <!-- mqttws31-component -->
   <mqttws31-component></mqttws31-component> 
</ng-container>

The isBrowser can be imported from(ng2)

import { isBrowser } from 'angular2-universal';

Or if ng4+, you can also define this in your browser module:

// app.browser
@NgModule({
  providers: [
    { provide: 'isBrowser', useValue: true }
  ]
})

then inject from constructor

export class SomeComponent implements OnInit {
  constructor(@Inject('isBrowser') private isBrowser: boolean)
  ngOnInit() { 
    // example usage, this could be anywhere in this Component of course
    if (this.isBrowser) { 
      alert('we're in the browser!');
    }
}
LeOn - Han Li
  • 9,388
  • 1
  • 65
  • 59
  • With latest Angular version, we can use angular provided injectors instead of manually providing **useValues**. Provided my solution below. – Mahesh Apr 20 '19 at 00:20
4

Angular 6 In server.ts use:

const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync('dist/browser/index.html').toString();
const win = domino.createWindow(template);

global['window'] = win;
global['document'] = win.document;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
global['HTMLElement'] = win.HTMLElement;
global['navigator'] = win.navigator;
3

window shouldn't be used in universal applications on server side, because Node.js doesn't have window, and having a dummy global.window currently affects the way Angular detects global variable.

If the package uses window, an issue can be opened in its repository and/or it can be forked and changed to not use window.

Since packages that rely on window often rely on things that are specific to client side, they won't work as expected on server side even if this issue is sorted out.

Package description says:

Depends on the library from: https://eclipse.org/paho/clients/js/

While library description says:

The Paho JavaScript Client is an MQTT browser-based client library written in Javascript that uses WebSockets to connect to an MQTT Broker.

Usually third-party Angular module that isn't supposed to work as expected on server side should be stubbed or mocked; dummy module with fake directives and services is imported in app.server.ts instead of real module.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 1
    Ok, I didn't really understand. Is there a way to solve this problem? Should I create a globa variable window within my node.js app and how and where should I do this? – mogli mogli Oct 20 '17 at 13:33
  • See the last paragraph. It's applicable to your case. It totally depends on how the module is used in your app, i.e. how this module affects the page that is generated on server side. Also, consider opening an issue. It's vendor's responsibility to provide a module that won't cause an error in universal app. – Estus Flask Oct 20 '17 at 13:38
  • I already opened the issue in the repo. I am using the module as client but the error is occuring on server side. I am not sure how I can solve this. However thanx for your help – mogli mogli Oct 20 '17 at 13:42
  • Do not import `ng2-mqtt` module in server app. If its import results in `Paho` global variable (I guess it is), create dummy `global.Paho` variable in server app that stubs or mocks the behaviour that is expected from this module. And actually, I don't see anything that is specific to Angular in this module, even though it has `ng2` prefix. It's just a library that exposes a global (that's why globals are bad). – Estus Flask Oct 20 '17 at 13:51
  • The point is that `Paho` global **shouldn't** work, because real ng2-mqtt **shouldn't** be imported on server side. Instead, `Paho` global should be replaced with dummy implementation of the module. Because real ng2-mqtt won't work in Node.js. – Estus Flask Oct 20 '17 at 15:15
  • Ok, I understood that I should not import ng2-mqtt on server side. After compiling it is automatically required in my server.js. How can I avoid this? With webpack? – mogli mogli Oct 20 '17 at 16:53
  • I'm not sure how it's possible. The package is included in the app only if it's imported. You should have `import 'ng2-mqtt/mqttws31.js'` in client-side bundle but don't have it in server-side bundle. And yes, you can also address this with webpack and substitute `ng2-mqtt/mqttws31.js` module with something else with [alias](https://webpack.github.io/docs/configuration.html#resolve-alias) feature. – Estus Flask Oct 20 '17 at 17:08
0

Work for me ng9+

// app.module.ts
@NgModule({
  providers: [
    { provide: 'isBrowser', useValue: true }
  ]
})

and replace absolute path (remove first /):

/assets/images/... to assets/images/...

Tan Nguyen
  • 947
  • 7
  • 8