window is not defined angular universal third library

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?

Answers:

Answer

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!');
    }
}
Answer

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.

Answer

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;
Answer

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.

Answer

Follow these two steps:

  1. Use PLATFORM_ID injection in your components to detect whether it is browser platform or server platform as below:

    constructor(
        @Inject(DOCUMENT) private document: Document,
        @Inject(PLATFORM_ID) private platformId: any) {}
    
    ngOnInit() {
        this.scrollToTop();
    }
    
    scrollToTop() {
        if (isPlatformBrowser(this.platformId)) {
            // your code
        }
    }
    

But this strategy will work for code which we write ourselves, but what if we are using a third party library which accesses window, document or other DOM globals?

JavaScript within a third party library may throw errors due to attempting to access these globals during our server render, but we have no control over the library’s code. In this case we can install a library such as domino, and then create a shim for the window and document objects in the server

  1. Install domino and use it as this in your server.ts file

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.