angularopenstreetmapserver-side-renderingangular-universalangular-openlayers

Angular 9 universal Server side rendering (SSR) error HTMLCanvasElement


Error: NotYetImplemented
    at HTMLCanvasElement.Wo4J.exports.nyi (/path/server/main.js:1:3906285)
    at rj.nf (/path/server/main.js:1:1929447)
    at /path/server/main.js:1:1930713
    at Object.ADia (/path/server/main.js:1:2061225)
    at __webpack_require__ (/path/server/main.js:1:295)
    at Object.7PEY (/path/server/main.js:1:1216808)
    at __webpack_require__ (/path/sen3aPro/server/main.js:1:295)
    at Object.d2mR (/path/server/main.js:1:5425768)
    at __webpack_require__ (/path/server/main.js:1:295)
    at Object.ZAI4 (/path/server/main.js:1:3975117)

i am geeting this error angular 9 server side rendering i am using open street map (OSM) and i think ngx-openlayers causes this problem cause when i remove it i got no error

I tried to load AngularOpenlayersModule module and the component using this module on client side only but no luck

my SharedModule.ts:

import {NgModule, PLATFORM_ID} from '@angular/core';
import {OsmViewComponent} from './osm-view/osm-view.component';
import {AngularOpenlayersModule} from 'ngx-openlayers';
import {CommonModule, isPlatformBrowser} from '@angular/common';

let osmC = [];
let ol = [];
if (isPlatformBrowser(PLATFORM_ID)) {
  osmC = [OsmViewComponent];
  ol = [AngularOpenlayersModule];
}

@NgModule({
  imports: [
    CommonModule,
    AngularOpenlayersModule
  ],
  declarations: [
    OsmViewComponent
  ],
exports: [
  OsmViewComponent
],
})
export class SharedModule {}

my dependencies are :

  "dependencies": {
    "@angular/animations": "~9.0.3",
    "@angular/cdk": "^9.1.2",
    "@angular/common": "~9.0.3",
    "@angular/compiler": "~9.1.3",
    "@angular/core": "~9.1.3",
    "@angular/fire": "^5.4.0",
    "@angular/forms": "~9.0.3",
    "@angular/platform-browser": "~9.0.3",
    "@angular/platform-browser-dynamic": "~9.0.3",
    "@angular/router": "~9.0.3",
    "@auth0/angular-jwt": "^4.2.0",
    "@fortawesome/fontawesome-free": "^5.12.1",
    "@types/chart.js": "^2.9.15",
    "animate.css": "^3.7.2",
    "boostrap": "^2.0.0",
    "bootstrap": "^4.1.3",
    "chart.js": "^2.9.3",
    "easy-pie-chart": "^2.1.7",
    "firebase": "^7.14.4",
    "get-blob-duration": "^1.1.0",
    "hammerjs": "^2.0.8",
    "lodash": "^4.17.15",
    "mdbootstrap": "^4.14.0",
    "ng-circle-progress": "^1.5.1",
    "ng-uikit-pro-standard": "file:ng-uikit-pro-standard-9.0.0.tgz",
    "ng2-charts": "^2.3.0",
    "ng5-slider": "^1.2.4",
    "ngx-device-detector": "^1.4.2",
    "ngx-openlayers": "^1.0.0-next.15",
    "ol": "^6.3.1",
    "openlayers": "^4.6.5",
    "recordrtc": "^5.5.9",
    "rxjs": "~6.5.4",
    "screenfull": "^3.3.0",
    "tslib": "^1.10.0",
    "units-converter": "^1.0.2",
    "zone.js": "~0.10.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.900.4",
    "@angular/cli": "~9.1.3",
    "@angular/compiler-cli": "~9.1.3",
    "@angular/language-service": "~9.0.3",
    "@types/node": "^12.11.1",
    "@types/jasmine": "~3.5.0",
    "@types/jasminewd2": "~2.0.3",
    "codelyzer": "^5.1.2",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.3.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~2.1.0",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.2",
    "protractor": "~5.4.3",
    "ts-node": "~8.3.0",
    "tslint": "~5.18.0",
    "typescript": "~3.7.5"
  }

Solution

  • Like I indicated with in this answer, open layers relies on the pixelworks lib, which tries to create a canvas element when the lib is imported (even if you are not actually instantiating an open layers map). This will not work server side and will cause the SSR process to crash

    My hack, based on the fact that I did not need to render the map server side, was to modify the server.js file (or main.js for angular 9+) to remove the offending bit of code

    sed -i "s/var context = document.createElement('canvas').getContext('2d');/var context = null;/" dist/server.js
    

    This will allow code execution to continue server side. The map will be initiated only in the browser, when the client side angular application takes over.

    In the component creating the map, you can also check if you are executing the code client or server side to only instantiate the map client side, using isPlatformbrowser