最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular Universal: navigator is not defined - Stack Overflow

programmeradmin3浏览0评论

I followed official angular-cli tutorial to integrate angular-universal to my existing angular-cli app.

I am able to do SSR for my angular-cli app. But when I try to integrate ngx-leaflet, I am getting following error:

ReferenceError: navigator is not defined at D:\ng2-ssr-pwa\dist\server.js:40251:29

Now, I understand that leaflet is trying to access navigator object which is not available in the Node context. So I decided to delay leaflet rendering until the page is loaded in the browser as given in this SO thread. But still I am getting same error. You can look the demo app with leaflet issue here.

./src/app/browserModuleLoader.service.ts:

import { Component, Inject, Injectable, OnInit, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Injectable()
export class BrowserModuleLoaderService {
    private _L: any;

    public constructor(@Inject(PLATFORM_ID) private _platformId: Object) {
        this._init();
    }

    public getL() {
        return this._safeGet(() => this._L);
    }

    private _init() {
        if (isPlatformBrowser(this._platformId)) {
            this._requireLegacyResources();
        }
    }

    private _requireLegacyResources() {
        this._L = require('leaflet');
    }

    private _safeGet(getCallcack: () => any) {
        if (isPlatformServer(this._platformId)) {
            throw new Error('invalid access to legacy component on server');
        }

        return getCallcack();
    }
}

./src/app/leaflet/app/leafletponent.ts:

// import * as L from 'leaflet';

import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, PLATFORM_ID } from '@angular/core';

import { BrowserModuleLoaderService } from '../browserModuleLoader.service';
import { isPlatformBrowser } from '@angular/common';

@Component({
    selector: 'app-leaflet',
    styleUrls: ['./leafletponent.scss'],
    template: `
      <div  *ngIf="isBrowser">
        <div leaflet [leafletOptions]="options"></div>
        </div>
  `,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LeafletComponent {
    isBrowser: boolean;
    options = {};

    constructor(private cdr: ChangeDetectorRef,
        @Inject(PLATFORM_ID) platformId: Object,
        private browserModuleLoaderService: BrowserModuleLoaderService
    ) {
        this.isBrowser = isPlatformBrowser(platformId);
    }

    ngAfterViewInit() {
        console.log('this.isBrowser ', this.isBrowser);
        if (this.isBrowser) {
            const L = this.browserModuleLoaderService.getL();
            this.options = {
                layers: [
                    L.tileLayer('http://{s}.tile.openstreetmap/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '...' }),
                ],
                zoom: 5,
                center: L.latLng({ lat: 38.991709, lng: -76.886109 }),
            };
        }
        this.cdr.detach();
    }

}

./src/app/appponent.html:

<div>
  <app-leaflet></app-leaflet>
</div>

How do I safely delay the leaflet rendering until the platform is not browser?

EDIT:

I removed all code related to leaflet (browserModuleLoader.service.ts, leafletponent.ts ect. ) and kept only leaflet module import in app.module.ts and actually this import is causing issue.

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

import { AppComponent } from './appponent';
import { BrowserModule } from '@angular/platform-browser';
// import { BrowserModuleLoaderService } from './browserModuleLoader.service';
// import { LeafletComponent } from './leaflet/leafletponent';
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
import { NgModule } from '@angular/core';

@NgModule({
  declarations: [
    AppComponent,
    // LeafletComponent
  ],
  imports: [
    BrowserModule.withServerTransition({appId: 'my-app'}),
    LeafletModule.forRoot()
  ],
  providers: [
    // BrowserModuleLoaderService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

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

import {AppComponent} from './appponent';
import {AppModule} from './app.module';
import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';
import {NgModule} from '@angular/core';
import {ServerModule} from '@angular/platform-server';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

How do I handle this nxg-leaflet module import?

I followed official angular-cli tutorial to integrate angular-universal to my existing angular-cli app.

I am able to do SSR for my angular-cli app. But when I try to integrate ngx-leaflet, I am getting following error:

ReferenceError: navigator is not defined at D:\ng2-ssr-pwa\dist\server.js:40251:29

Now, I understand that leaflet is trying to access navigator object which is not available in the Node context. So I decided to delay leaflet rendering until the page is loaded in the browser as given in this SO thread. But still I am getting same error. You can look the demo app with leaflet issue here.

./src/app/browserModuleLoader.service.ts:

import { Component, Inject, Injectable, OnInit, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Injectable()
export class BrowserModuleLoaderService {
    private _L: any;

    public constructor(@Inject(PLATFORM_ID) private _platformId: Object) {
        this._init();
    }

    public getL() {
        return this._safeGet(() => this._L);
    }

    private _init() {
        if (isPlatformBrowser(this._platformId)) {
            this._requireLegacyResources();
        }
    }

    private _requireLegacyResources() {
        this._L = require('leaflet');
    }

    private _safeGet(getCallcack: () => any) {
        if (isPlatformServer(this._platformId)) {
            throw new Error('invalid access to legacy component on server');
        }

        return getCallcack();
    }
}

./src/app/leaflet/app/leaflet.component.ts:

// import * as L from 'leaflet';

import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, PLATFORM_ID } from '@angular/core';

import { BrowserModuleLoaderService } from '../browserModuleLoader.service';
import { isPlatformBrowser } from '@angular/common';

@Component({
    selector: 'app-leaflet',
    styleUrls: ['./leaflet.component.scss'],
    template: `
      <div  *ngIf="isBrowser">
        <div leaflet [leafletOptions]="options"></div>
        </div>
  `,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LeafletComponent {
    isBrowser: boolean;
    options = {};

    constructor(private cdr: ChangeDetectorRef,
        @Inject(PLATFORM_ID) platformId: Object,
        private browserModuleLoaderService: BrowserModuleLoaderService
    ) {
        this.isBrowser = isPlatformBrowser(platformId);
    }

    ngAfterViewInit() {
        console.log('this.isBrowser ', this.isBrowser);
        if (this.isBrowser) {
            const L = this.browserModuleLoaderService.getL();
            this.options = {
                layers: [
                    L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '...' }),
                ],
                zoom: 5,
                center: L.latLng({ lat: 38.991709, lng: -76.886109 }),
            };
        }
        this.cdr.detach();
    }

}

./src/app/app.component.html:

<div>
  <app-leaflet></app-leaflet>
</div>

How do I safely delay the leaflet rendering until the platform is not browser?

EDIT:

I removed all code related to leaflet (browserModuleLoader.service.ts, leaflet.component.ts ect. ) and kept only leaflet module import in app.module.ts and actually this import is causing issue.

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

import { AppComponent } from './app.component';
import { BrowserModule } from '@angular/platform-browser';
// import { BrowserModuleLoaderService } from './browserModuleLoader.service';
// import { LeafletComponent } from './leaflet/leaflet.component';
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
import { NgModule } from '@angular/core';

@NgModule({
  declarations: [
    AppComponent,
    // LeafletComponent
  ],
  imports: [
    BrowserModule.withServerTransition({appId: 'my-app'}),
    LeafletModule.forRoot()
  ],
  providers: [
    // BrowserModuleLoaderService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

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

import {AppComponent} from './app.component';
import {AppModule} from './app.module';
import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';
import {NgModule} from '@angular/core';
import {ServerModule} from '@angular/platform-server';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

How do I handle this nxg-leaflet module import?

Share Improve this question edited Apr 15, 2018 at 7:23 Saurabh Palatkar asked Apr 15, 2018 at 5:43 Saurabh PalatkarSaurabh Palatkar 3,38412 gold badges52 silver badges112 bronze badges 8
  • Not sure if it'd work, but try <div *ngIf="isBrowser" leaflet [leafletOptions]="options"></div> – David Commented Apr 15, 2018 at 5:54
  • still the same issue. – Saurabh Palatkar Commented Apr 15, 2018 at 6:22
  • Is it the only place where you are using it? The other solution would be not importing ngx-leaflet modulein your module.server.ts file – David Commented Apr 15, 2018 at 6:34
  • I am not importing ngx-leaflet in app.server.module.ts and importing it only in app.module.ts. – Saurabh Palatkar Commented Apr 15, 2018 at 6:47
  • And in your app.server.module you are not importing 'app.module' ? If not, then it means that ngx-lealeft might not be the culprit. You should look at the server.js to try to see where it's coming from exactly – David Commented Apr 15, 2018 at 6:50
 |  Show 3 more comments

4 Answers 4

Reset to default 13

Solved this issue by using Mock Browser.

server.ts:

const MockBrowser = require('mock-browser').mocks.MockBrowser;
const mock = new MockBrowser();


global['navigator'] = mock.getNavigator();

I too was receiving this error with Angular Universal. Using the Mock Browser from the solution above did fix this error for me, but it also started a new Warning related to CommonJS. Rather than digging into that issue, I realized I was already using Domino in my server.ts file, so I could easily set the navigator with Domino. This is what worked best for me:

npm install domino

server.ts:

const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs
  .readFileSync(path.join('dist/<your-app-name>/browser', 'index.html')) //<--- REPLACE WITH YOUR APP NAME
  .toString();
const window = domino.createWindow(template);
global['window'] = window;
global['document'] = window.document;
global['navigator'] = window.navigator;

@2024 Angular 17
I received the warning that Mock Browser uses deprecated components and after some research I discovered that Navigator is a window object and so if you intend to use the browser you can add a server check, take a look:

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

export class AppComponent {

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object
  ) {
    if (isPlatformBrowser(this.platformId)) {
      // Use Navigator
    }
  }
}

Fixed by (global as any).navigator = win.navigator;, much more elegant, definitely native, and without relying on outdated Mock Browser.

发布评论

评论列表(0)

  1. 暂无评论