【问题标题】:"window is not defined" in Angular service yet code works perfectlyAngular 服务中的“未定义窗口”但代码运行良好
【发布时间】:2020-03-19 09:11:29
【问题描述】:

我在 Angular 8 服务中使用 indexeddb,需要 window。代码构建没有错误,应用程序完美地创建了 db objectstore。但是在生产模式下的运行时(使用实际的节点服务器而不是 ng serve 不会发生此错误),我在终端运行 angular 时收到此错误:

ERROR ReferenceError: window is not defined
    at IndexedDBService.isSupported (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:71199:9)
    at IndexedDBService.openDB (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:71203:18)
    at Promise (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:72026:46)

同样,如果 window 实际上是未定义的,isSupported() 函数将停止运行 openDB()。浏览器控制台也没有错误。

这是我服务的相关部分。

@Injectable()
export class IndexedDBService {
  isSupported(): boolean {
    return !!window.indexedDB;
  }

  openDB(dbName: string,
         version: number,
         onUpgradeNeededCallback: OnUpgradeNeededCallback,
         onSuccessCallback: OnOpenSuccessCallback,
         onErrorCallback: OnOpenErrorCallback,
         onBlockedCallback: OnOpenBlockedCallback): Observable<IDBOpenDBRequest> {
    let openDBRequest: IDBOpenDBRequest = null;
    if (this.isSupported()) {
      openDBRequest = window.indexedDB.open(dbName, version);
      openDBRequest.onupgradeneeded = onUpgradeNeededCallback;
      openDBRequest.onsuccess = onSuccessCallback;
      openDBRequest.onerror = onErrorCallback;
      openDBRequest.onblocked = onBlockedCallback;
    }
    return of(openDBRequest);
  }

那里有许多建议的“解决方案”,主要归结为通过服务或普通注入提供它(例如,本博客中的第 1 点 https://willtaylor.blog/angular-universal-gotchas/)但它所做的只是通过注入从其他一些服务传递窗口到我的。但是我的代码可以正常工作,所以它显然可以访问窗口...

更新:

组件的ngOnInit() 中的以下行与“未定义”的 Worker 存在相同的问题,但该 Worker 已加载并完美运行:

const offlineProductsWorker = new Worker('webworkers/offline-products-worker.js');

更新2:

我找到了一个解决方案(发布在下面),但检查服务器端渲染似乎更像是一种解决方法,而不是解决服务器端渲染正在发生的事实(不确定是否应该是这种情况)。

我将在下面包含我与 webpack 一起使用的 server.ts 脚本。这是对另一个项目的修改,我不明白其中的大部分内容。如果有人可以向我指出我可以改变什么来停止服务器端渲染,那就太好了。或者,如果它应该这样做,那为什么?

// tslint:disable:ish-ordered-imports no-console
import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
import * as https from 'https';
import * as fs from 'fs';

/*
 * Load config from .env file
 */
require('dotenv').config({ path: './ng-exp/.env' });
const IS_HTTPS = process.env.IS_HTTPS === 'true';
const SSL_PATH = process.env.SSL_PATH;
const ENVIRONMENT = process.env.ENVIRONMENT;

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

const logging = !!process.env.LOGGING;

// Express server
const app = express();

const PORT = process.env.PORT || 4200;
const DIST_FOLDER = process.cwd();

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

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

// 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', join(DIST_FOLDER, 'ng-exp'));

// Server static files from /browser
app.get(
  '*.*',
  express.static(join(DIST_FOLDER, 'ng-exp'), {
    setHeaders: (res, path) => {
      if (/\.[0-9a-f]{20,}\./.test(path)) {
        // file was output-hashed -> 1y
        res.set('Cache-Control', 'public, max-age=31557600');
      } else {
        // file should be re-checked more frequently -> 5m
        res.set('Cache-Control', 'public, max-age=300');
      }
    },
  })
);

// ALl regular routes use the Universal engine
app.get('*', (req: express.Request, res: express.Response) => {
  if (logging) {
    console.log(`GET ${req.url}`);
  }
  res.render(
    'index',
    {
      req,
      res,
    },
    (err: Error, html: string) => {
      res.status(html ? res.statusCode : 500).send(html || err.message);
      if (logging) {
        console.log(`RES ${res.statusCode} ${req.url}`);
        if (err) {
          console.log(err);
        }
      }
    }
  );
});

const sslOptions = {
  key: fs.readFileSync(`${SSL_PATH}/${ENVIRONMENT}/server.key`),
  cert: fs.readFileSync(`${SSL_PATH}/${ENVIRONMENT}/server.crt`),
};

// Start up the Node server
let server;
if (IS_HTTPS) {
  server = https.createServer(sslOptions, app);
} else {
  server = app;
}
server.listen(PORT, () => {
  console.log(`Node Express server listening on http${IS_HTTPS ? 's' : ''}://localhost:${PORT}`);
  const icmBaseUrl = process.env.ICM_BASE_URL;
  if (icmBaseUrl) {
    console.log('ICM_BASE_URL is', icmBaseUrl);
  }
});

【问题讨论】:

  • 你在做服务器端渲染对吗?根据堆栈跟踪,您实际上陷入了isSupported() 方法。所以我认为这行不通。
  • 但它正在工作。我可以在 chrome 开发人员的控制台中看到对象存储,并且每次测试时都会清除完整的 indexedDB。至于服务器端渲染,我不完全确定,但我认为对于网站,Angular 代码提供给浏览器,浏览器完成所有工作。
  • 我认为您还应该检查它是否在节点环境中运行。如果是,它应该返回 false。如果有帮助,请参阅this
  • server.js 表示您在此处进行服务器端渲染。因此,当服务器尝试渲染视图时,可能会出现这些错误,因为服务器上没有浏览器和窗口对象。然后所谓的hydrating 启动,角代码在客户端执行,然后一切正常。您可能需要添加一些 if (isPlatformBrowser()) 检查以避免此代码在服务器端运行,或者完全禁用服务器端呈现。

标签: node.js angular webpack indexeddb


【解决方案1】:

这里有一个相关的问题: https://github.com/hellosign/hellosign-embedded/issues/107 基本上,为了避免错误,您可以在全局窗口的某处声明。

if (typeof window === 'undefined') {
    global.window = {}
}

我还找到了React JS Server side issue - window not found,它更好地解释了这个问题以及它为什么在客户端有效。

【讨论】:

  • 如果我尝试这样做,我会收到编译错误Cannot find name 'global'
【解决方案2】:

感谢 ChrisY 的一些意见,我找到了解决方案

我使用 webpack 部署我的代码并使用 node.js 运行它。似乎节点以某种方式呈现它服务器端,然后浏览器也呈现它。服务器站点部分对店面没有影响,但会导致(看似无害的)错误。在isSupported() 中,我添加了console.log(isPlatformBrowser(this.platformId)),它在服务器终端中打印为false,但在浏览器中打印为true。因此,我将代码更改如下:

constructor(@Inject(PLATFORM_ID) private platformId: any) {}

isSupported(): boolean {
  return isPlatformBrowser(this.platformId) && !!indexedDB;
}

现在它仍然像以前一样在浏览器中工作,但没有服务器错误。

更新:

我也找到了服务器端渲染的原因。描述中的server.ts 文件有一个带有res.render( 的块。这首先在服务器上呈现页面,如果它没有收到 html,则返回状态代码 500。否则它允许客户端呈现它。鉴于这是一个现实的场景,我决定在我的代码中保留额外的 isPlatformBrowser(this.platformId) 检查。然后应该对只能由客户端执行的任何操作(窗口、dom、worker 等)执行此操作。

不是没有服务器端渲染,res.render( 块的替代方案是

res.status(200).sendFile(`/`, {root: join(DIST_FOLDER, 'ng-exp')});

【讨论】:

    猜你喜欢
    • 2017-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多