Angular SSR应用集成CalHeatMap遇document未定义问题求助
问题:Angular SSR集成CalHeatMap时出现"document is not defined"错误
我尝试将CalHeatMap集成到Angular独立组件中,组件代码如下:
import { Component } from '@angular/core'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { matWavingHandOutline } from '@ng-icons/material-icons/outline'; import CalHeatMap from 'cal-heatmap'; @Component({ selector: 'app-dashboard-summary', standalone: true, imports: [NgIconComponent], templateUrl: './dashboard-summary.component.html', styleUrl: './dashboard-summary.component.css', viewProviders: [provideIcons({ matWavingHandOutline })], }) export class DashboardSummaryComponent { ngOnInit() { let cal = new CalHeatMap(); // cal.paint({}); } }
但调用cal.paint({})时触发错误:
ReferenceError: document is not defined
错误源于CalHeatMap内部的这段代码:
import {Selection, root} from "./selection/index.js"; export default function(selector) { return typeof selector === "string" ? new Selection([[document.querySelector(selector)]], [document.documentElement]) : new Selection([[selector]], root); }
我尝试通过JSDOM模拟document环境,但仍报错,请问如何在SSR应用中成功集成CalHeatMap?
解决方案
核心思路:CalHeatMap依赖浏览器原生DOM环境,SSR过程中服务器端不存在document等浏览器API,因此必须确保CalHeatMap的初始化和渲染逻辑仅在客户端执行,避免服务器端触发相关代码。
方法1:通过isPlatformBrowser判断执行环境
注入Angular的PLATFORM_ID,用isPlatformBrowser确认当前为客户端环境后,再初始化CalHeatMap:
import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { matWavingHandOutline } from '@ng-icons/material-icons/outline'; import CalHeatMap from 'cal-heatmap'; @Component({ selector: 'app-dashboard-summary', standalone: true, imports: [NgIconComponent], templateUrl: './dashboard-summary.component.html', styleUrl: './dashboard-summary.component.css', viewProviders: [provideIcons({ matWavingHandOutline })], }) export class DashboardSummaryComponent implements OnInit { constructor(@Inject(PLATFORM_ID) private platformId: Object) {} ngOnInit() { // 仅在浏览器环境中执行CalHeatMap初始化 if (isPlatformBrowser(this.platformId)) { const cal = new CalHeatMap(); cal.paint({ // 指定渲染的容器元素(需在模板中提前定义) itemSelector: '#cal-heatmap-container' }); } } }
同时在组件模板中添加渲染容器:
<div id="cal-heatmap-container"></div>
方法2:动态导入CalHeatMap(推荐)
通过动态导入语法,确保CalHeatMap库仅在客户端被加载,从根源避免服务器端解析该库时触发DOM相关错误:
import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { matWavingHandOutline } from '@ng-icons/material-icons/outline'; @Component({ selector: 'app-dashboard-summary', standalone: true, imports: [NgIconComponent], templateUrl: './dashboard-summary.component.html', styleUrl: './dashboard-summary.component.css', viewProviders: [provideIcons({ matWavingHandOutline })], }) export class DashboardSummaryComponent implements OnInit { constructor(@Inject(PLATFORM_ID) private platformId: Object) {} async ngOnInit() { if (isPlatformBrowser(this.platformId)) { // 动态导入CalHeatMap,仅在客户端执行 const CalHeatMap = (await import('cal-heatmap')).default; const cal = new CalHeatMap(); cal.paint({ itemSelector: '#cal-heatmap-container' }); } } }
方法3:结合AfterViewInit钩子确保DOM就绪
使用ngAfterViewInit钩子,确保组件DOM元素完全渲染后再初始化CalHeatMap,避免因容器未加载导致的选择器错误:
import { Component, AfterViewInit, Inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { matWavingHandOutline } from '@ng-icons/material-icons/outline'; import CalHeatMap from 'cal-heatmap'; @Component({ selector: 'app-dashboard-summary', standalone: true, imports: [NgIconComponent], templateUrl: './dashboard-summary.component.html', styleUrl: './dashboard-summary.component.css', viewProviders: [provideIcons({ matWavingHandOutline })], }) export class DashboardSummaryComponent implements AfterViewInit { constructor(@Inject(PLATFORM_ID) private platformId: Object) {} ngAfterViewInit() { if (isPlatformBrowser(this.platformId)) { const cal = new CalHeatMap(); cal.paint({ itemSelector: '#cal-heatmap-container' }); } } }
为什么修改CalHeatMap源码无效?
你尝试用JSDOM模拟document的方法无法解决问题,原因在于:
- SSR过程中,Angular服务器端的模块加载逻辑与客户端不同,修改本地源码可能未被正确识别或加载
- JSDOM模拟的DOM环境与真实浏览器存在差异,CalHeatMap可能依赖
window、navigator等其他浏览器API,仅模拟document不足以覆盖所有依赖
内容的提问来源于stack exchange,提问作者user28273827332
相关产品推荐
相关产品推荐

