"window is not defined" 在 Angular 服务中,但代码运行良好

"window is not defined" in Angular service yet code works perfectly

我在 Angular 8 服务中使用 indexeddb 并且需要 window。代码构建没有错误,应用程序完美地创建了数据库对象存储。但是在 运行 生产模式下(使用实际的节点服务器而不是 ng serve 时不会发生此错误),我在终端 运行ning 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);
  }

有很多建议 "solutions" 主要归结为通过服务或普通注入提供它(例如本博客中的第 1 点 https://willtaylor.blog/angular-universal-gotchas/),但它所做的只是通过 window 通过注入到我的一些其他服务。但是我的代码有效,所以它显然可以访问 window...

更新:

组件 ngOnInit() 中的以下行与 Worker 存在相同的问题 "not defined" 但 worker 已加载并且 运行s 完美:

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

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

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

我还发现 更好地解释了问题以及为什么它在客户端有效。

感谢 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,它会收到 returns 状态代码 500。否则它允许客户端呈现它。鉴于这是一个现实的场景,我决定在我的代码中保留额外的 isPlatformBrowser(this.platformId) 检查。然后应该对只能由客户执行的任何事情(window、dom、工作人员等)执行此操作。

不是没有服务器端呈现,res.render( 块的替代方法是

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