Angular 通用(SSR),带有 Leaflet 和 ngx-leaflet

Angular Universal (SSR), with Leaflet and ngx-leaflet

我刚接触 angular(更不用说菜鸟了),由于 Leaflet 的问题,我正在努力将标准 Angular 应用程序传递给 Angular Universal,我发现许多项目的例子都运行良好,但我无法让它在我的项目中以相同的方式工作。 我设法隔离了问题,但无法解决。 我删除了所有组件和模块中对传单的所有引用,只留下 package.json 如下:

  "name": "vyv-angular",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "dev:ssr": "ng run vyv-angular:serve-ssr",
    "serve:ssr": "node dist/vyv-angular/server/main.js",
    "build:ssr": "ng build --prod && ng run vyv-angular:server:production",
    "prerender": "ng run vyv-angular:prerender"
  },
  "private": true,
  "dependencies": {
    "@agm/core": "3.0.0-beta.0",
    "@angular/animations": "10.1.5",
    "@angular/cdk": "10.2.4",
    "@angular/common": "10.1.5",
    "@angular/compiler": "10.1.5",
    "@angular/core": "10.1.5",
    "@angular/fire": "6.0.3",
    "@angular/forms": "10.1.5",
    "@angular/localize": "10.1.5",
    "@angular/material": "10.2.4",
    "@angular/platform-browser": "^10.1.5",
    "@angular/platform-browser-dynamic": "10.1.5",
    "@angular/platform-server": "^10.1.5",
    "@angular/router": "10.1.5",
    "@asymmetrik/ngx-leaflet": "8.1.0",
    "@ng-bootstrap/ng-bootstrap": "7.0.0",
    "@ng-bootstrap/schematics": "2.0.0-alpha.1",
    "@ng-select/ng-select": "5.0.3",
    "@ngrx/store": "10.0.0",
    "@nguniversal/express-engine": "10.1.0",
    "@nguniversal/module-map-ngfactory-loader": "8.2.6",
    "@ngx-translate/core": "13.0.0",
    "@ngx-translate/http-loader": "6.0.0",
    "@swimlane/ngx-datatable": "18.0.0",
    "acorn": "8.0.1",
    "agm-direction": "0.8.7",
    "body-parser": "1.19.0",
    "bootstrap": "4.0.0",
    "classlist.js": "1.1.20150312",
    "core-js": "3.6.5",
    "cors": "2.8.5",
    "d3": "6.1.1",
    "datatables.net": "1.10.22",
    "date-fns": "2.16.1",
    "express": "4.17.1",
    "firebase": "7.23.0",
    "google-libphonenumber": "^3.2.13",
    "hopscotch": "0.3.1",
    "intl": "1.2.5",
    "intl-tel-input": "17.0.6",
    "leaflet": "1.7.1",
    "leaflet-ant-path": "1.3.0",
    "material-design-icons": "3.0.1",
    "md5-typescript": "1.0.5",
    "moment": "2.28.0",
    "ng-apexcharts": "1.5.1",
    "ng-click-outside": "7.0.0",
    "ngx-bootstrap": "^6.1.0",
    "ngx-custom-validators": "9.1.0",
    "ngx-device-detector": "2.0.0",
    "ngx-intl-tel-input": "^3.0.3",
    "ngx-perfect-scrollbar": "10.0.1",
    "ngx-quill": "12.0.1",
    "ngx-spinner": "10.0.1",
    "ngx-take-until-destroy": "5.4.0",
    "ngx-toastr": "13.0.0",
    "ngx-ui-switch": "10.0.2",
    "node-sass": "4.14.1",
    "nodemailer": "6.4.13",
    "nouislider": "14.6.2",
    "popper.js": "1.16.1",
    "postcss-rtl": "1.7.3",
    "prismjs": "1.21.0",
    "pug": "3.0.0",
    "quill": "1.3.7",
    "rxjs": "6.6.3",
    "rxjs-compat": "^6.6.3",
    "screenfull": "5.0.2",
    "tslib": "2.0.1",
    "web-animations-js": "2.3.2",
    "zone.js": "0.11.1"
  },
  "devDependencies": {
    "@angular-devkit/architect": "0.1001.6",
    "@angular-devkit/build-angular": "0.1001.6",
    "@angular/cli": "10.1.6",
    "@angular/compiler-cli": "10.1.5",
    "@nguniversal/builders": "10.1.0",
    "@types/chartist": "0.11.0",
    "@types/cors": "2.8.8",
    "@types/d3-shape": "1.3.2",
    "@types/express": "4.17.0",
    "@types/google-maps": "3.2.2",
    "@types/googlemaps": "3.39.12",
    "@types/jasmine": "3.5.0",
    "@types/jasminewd2": "2.0.3",
    "@types/jquery": "3.5.2",
    "@types/node": "14.11.8",
    "@types/nodemailer": "6.4.0",
    "codelyzer": "6.0.1",
    "firebase-admin": "9.2.0",
    "firebase-functions": "3.6.0",
    "firebase-functions-test": "0.2.2",
    "firebase-tools": "8.12.1",
    "fuzzy": "0.1.3",
    "inquirer": "6.2.2",
    "inquirer-autocomplete-prompt": "1.0.1",
    "jasmine-core": "3.6.0",
    "jasmine-spec-reporter": "4.2.1",
    "karma": "5.0.0",
    "karma-chrome-launcher": "3.1.0",
    "karma-coverage-istanbul-reporter": "2.1.0",
    "karma-jasmine": "3.0.1",
    "karma-jasmine-html-reporter": "1.4.2",
    "open": "7.3.0",
    "protractor": "7.0.0",
    "rxjs-compat": "6.6.3",
    "tslint": "6.1.0",
    "typescript": "4.0.3"
  }
}

然后

ng run dev:ssr

运行良好,应用程序可与服务器端渲染一起使用(当然除了映射部分,因为它已被删除)。

接下来开始一步步重新引入ngx-leaflet模块。 第一步是简单地在 app.module.ts

中添加模块
import {LeafletModule} from '@asymmetrik/ngx-leaflet';

在进口部分:

 imports: [
LeafletModule,

只是添加这个,没有在任何组件中使用(我将我的地图组件留空) 给我以下错误:

    > ng run vyv-angular:serve-ssr

****************************************************************************************
This is a simple server for use in testing or debugging Angular applications locally.
It hasn't been reviewed for security issues.

DON'T USE IT FOR PRODUCTION!
****************************************************************************************
Hash: c84349b0ffb562a756f2
Time: 40944ms
Built at: 2020-10-11 9:17:28 ├F10: AM┤
      Asset      Size  Chunks                          Chunk Names
    main.js  13.3 MiB    main  [emitted]        [big]  main
main.js.map  13.2 MiB    main  [emitted] [dev]         main
Entrypoint main [big] = main.js main.js.map
chunk {main} main.js, main.js.map (main) 11.4 MiB [entry] [rendered]

chunk {main} main.js, main.js.map (main) 1.36 MB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 147 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.15 kB [entry] [rendered]
chunk {styles} styles.css, styles.css.map (styles) 1.36 MB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 8.77 MB [initial] [rendered]
Date: 2020-10-11T14:17:45.082Z - Hash: bde98637bf23c20bf894 - Time: 50331ms

Compiled successfully.
F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:65133
  var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
                  ^

ReferenceError: window is not defined
    at F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:65133:19
    at F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:64914:11
    at Object.4R65 (F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:64916:2)
    at __webpack_require__ (F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:20:30)
    at F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:116999:129
    at Object.RlJC (F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:117001:2)
    at __webpack_require__ (F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:20:30)
    at Module.ZAI4 (F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:134326:82)
    at __webpack_require__ (F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:20:30)
    at Module.24aS (F:\GitaLab\vyv-angular\dist\vyv-angular\server\main.js:54859:69)

A server error has occurred.
node exited with 1 code.
connect ECONNREFUSED 127.0.0.1:58155

我发现“var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;”行在 leaflet-src.js 内,在 node-modules/ 下,所以它是传单 javascript 对 window 的引用失败,这是正常的,因为 运行 在服务器上,但如果我什至不触发脚本的任何使用,为什么会出现此错误? 我设法克隆了工作示例的存储库,但 package.json 、 app.module.ts 和其他组件与我的相同。所以问题出在其他地方,但我找不到哪里。 我是否只是以错误的方式将我的标准 Angular 应用程序迁移到 Angular Universal? 我遵循了这个 运行 的标准指南:

ng add @nguniversal/express-engine

一切顺利。

我然后我也运行 :

ng add @angular/fire

为了能够在 firebase 中作为一个函数进行部署,但目前问题不在于部署部分,SSR 服务器不使用 app.module.ts 中导入的 ngx-leaflet 模块启动.

我是否应该添加任何其他文件来提供帮助? ng add @nguniversal/express-engine 为服务器和 ts 配置创建了新文件,但我没有修改它们...

欢迎任何让我走上正轨的想法或评论!

编辑:

我克隆的工作示例和我的工作示例之间的唯一区别是 ngx 模块的导入是在仅通过路由器加载的子模块中完成的。 我尝试在这些项目中将导入添加到主 app.modules.ts 中,但出现了同样的错误!! 所以这个问题似乎只发生在这个模块的导入处于顶层时。 我会尝试在一个单独的模块中实现我的地图组件,如果我找到一个可用的解决方案,我会保持这个 post 更新...

错误信息很清楚,这是因为你使用了window对象。只有在浏览器内部才能识别浏览器并执行代码如下:

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

@Component({
  selector: 'app-yourcomponent',
  templateUrl: './app.yourcomponent.html',
})
export class YourComponent implements OnInit {
  constructor(
    @Inject(PLATFORM_ID) private _platformId
  ) {}

  ngOnInit() {
    if (isPlatformBrowser(this._platformId)) {
      // Whatever you want to run inside the browser
      window.scrollTo(0, 0);
    }
  }
}

经过几天的苦恼,我决定放弃 ngx-leaflet 模块。 我设法让传单地图与原始 javascript 库一起使用,基于 Sergey 从这个问题中提出的想法:How to use @angular/universal with Leaflet?。 这意味着对组件进行一些修改,因为 ngx-leaflet 提供的 angular 中的传单集成的便利性已经丢失,但这是值得的。

正如 Sergey 所解释的那样,解决方案是将对传单脚本的访问包装在一个服务中,但是在我的项目中,服务中使用的“require”javascript 方法没有被识别,这甚至如果“@types/node”已正确安装。下一个问题的解决方案是修改 tsconfig.app.json 并在编译器选项中添加以下内容:"types": [ "node" ],在我的案例中结果如下:

  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": [ "node" ],
    "typeRoots": [ "../node_modules/@types" ]
  },

因为它已经出现在 tsconfig.server.json 中,但这还不够。

然后在你的组件中(也如 Sergei 所解释的那样),注意不要使用或导入任何 Leflet 方法,“@types/leaflet”没问题。 所有对“L”传单脚本的引用都必须通过服务完成,嵌入测试浏览器存在的测试中:

  if (isPlatformBrowser(this.platformId)) {
     this.leafletService.L.
  }

您必须将传单导入包装到服务中。工作示例可以从 github 此处下载:https://github.com/NickToony/universal-starter-leaflet-example

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

@Injectable()
export class MapService {

  public L = null;

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {
    if (isPlatformBrowser(platformId)) {
      this.L = require('leaflet');
    }
  }

}