adam lakhmiri
adam lakhmiri

Reputation: 11

I would like to use apexcharts and ng-apexcharts in angular 18 with SSR

I would like to use apexcharts and ng-apexcharts in angular 18 with SSR , i have this problem:

window is not defined at node_modules/apexcharts/dist/apexcharts.common.js (c:/Users/lakhm/Desktop/cod_manager/node_modules/apexcharts/dist/apexcharts.common.js:6:398992) at __require2 (C:/Users/lakhm/Desktop/cod_manager/.angular/vite-root/cod_manager/chunk-IOFBTDQ2.mjs:47:50) at eval (c:/Users/lakhm/Desktop/cod_manager/node_modules/ng-apexcharts/fesm2020/ng-apexcharts.mjs:4:24) at async instantiateModule (file:///C:/Users/lakhm/Desktop/cod_manager/node_modules/vite/dist/node/chunks/dep-cNe07EU9.js:55058:9

Upvotes: 1

Views: 1140

Answers (1)

Naren Murali
Naren Murali

Reputation: 56054

window and document does not exist on the server. So when you use SSR in your angular application. The code rendering on the server has window as undefined. To solve this below are the steps to implement.

If you add the script "apexcharts/dist/apexcharts.min.js" to angular.json, it will run on both the server and the browser, which is bad since the package uses window. The solution for this will be to add the script only to main.server.ts

main.server.ts

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
import 'apexcharts/dist/apexcharts.min.js'; // <- changed here!

bootstrapApplication(AppComponent, appConfig).catch((err) =>
  console.error(err)
);

Then you can use the below method to convert the ng-apexcharts package to support SSR. The most important part is that @defer block runs only on the browser ( Thanks to Matthieu Riegler for the hint on reddit ), so we have to create two components, the first component is just a dummy wrapper component, that passes all the supported props from the main component to an inner component. The component is called app-apx-chart-ssr This component uses @defer to wrap another component that contains the apex chart. The defer block ensures the content loads only on the browser and no error occour.

apx-chart-ssr.html

@defer() {
<app-apx-chart
  [chart]="chart"
  [annotations]="annotations"
  [colors]="colors"
  [dataLabels]="dataLabels"
  [series]="series"
  [stroke]="stroke"
  [labels]="labels"
  [legend]="legend"
  [markers]="markers"
  [noData]="noData"
  [fill]="fill"
  [tooltip]="tooltip"
  [plotOptions]="plotOptions"
  [responsive]="responsive"
  [xaxis]="xaxis"
  [yaxis]="yaxis"
  [forecastDataPoints]="forecastDataPoints"
  [grid]="grid"
  [states]="states"
  [title]="title"
  [subtitle]="subtitle"
  [theme]="theme"
  [autoUpdateSeries]="autoUpdateSeries"
  (chartReady)="chartReady.next($event)"
/>
}

apx-chart-ssr.ts

import { Component, EventEmitter, Input, Output } from '@angular/core';
import {
  ApexTheme,
  NgApexchartsModule,
  ApexChart,
  ApexAnnotations,
  ApexDataLabels,
  ApexAxisChartSeries,
  ApexNonAxisChartSeries,
  ApexStroke,
  ApexLegend,
  ApexMarkers,
  ApexNoData,
  ApexFill,
  ApexTooltip,
  ApexPlotOptions,
  ApexResponsive,
  ApexXAxis,
  ApexYAxis,
  ApexForecastDataPoints,
  ApexGrid,
  ApexStates,
  ApexTitleSubtitle,
} from 'ng-apexcharts';
import { ApxChartComponent } from './apx-chart/apx-chart.component';

@Component({
  selector: 'app-apx-chart-ssr',
  standalone: true,
  imports: [ApxChartComponent],
  templateUrl: './apx-chart-ssr.component.html',
})
export class ApxChartSsrComponent {
  @Input() chart!: ApexChart;
  @Input() annotations!: ApexAnnotations;
  @Input() colors!: any[];
  @Input() dataLabels!: ApexDataLabels;
  @Input() series!: ApexAxisChartSeries | ApexNonAxisChartSeries;
  @Input() stroke!: ApexStroke;
  @Input() labels!: string[];
  @Input() legend!: ApexLegend;
  @Input() markers!: ApexMarkers;
  @Input() noData!: ApexNoData;
  @Input() fill!: ApexFill;
  @Input() tooltip!: ApexTooltip;
  @Input() plotOptions!: ApexPlotOptions;
  @Input() responsive!: ApexResponsive[];
  @Input() xaxis!: ApexXAxis;
  @Input() yaxis!: ApexYAxis | ApexYAxis[];
  @Input() forecastDataPoints!: ApexForecastDataPoints;
  @Input() grid!: ApexGrid;
  @Input() states!: ApexStates;
  @Input() title!: ApexTitleSubtitle;
  @Input() subtitle!: ApexTitleSubtitle;
  @Input() theme!: ApexTheme;

  @Input() autoUpdateSeries = true;

  @Output() chartReady = new EventEmitter();
}

Then in the inner component app-apx-chart you will see the important import imports: [NgApexchartsModule] (This import loads only on the browser due to defer, where window exists, without defer, you will get window undefined error).

Other than this the inner component serves to use the import only on browser and not much, same as the above component, just to pass props to the package component (apx-chart).

apx-chart.component.ts

import { Component, EventEmitter, Input, Output } from '@angular/core';
import {
  ApexTheme,
  NgApexchartsModule,
  ApexChart,
  ApexAnnotations,
  ApexDataLabels,
  ApexAxisChartSeries,
  ApexNonAxisChartSeries,
  ApexStroke,
  ApexLegend,
  ApexMarkers,
  ApexNoData,
  ApexFill,
  ApexTooltip,
  ApexPlotOptions,
  ApexResponsive,
  ApexXAxis,
  ApexYAxis,
  ApexForecastDataPoints,
  ApexGrid,
  ApexStates,
  ApexTitleSubtitle,
} from 'ng-apexcharts';

@Component({
  selector: 'app-apx-chart',
  standalone: true,
  imports: [NgApexchartsModule],
  templateUrl: './apx-chart.component.html',
  styleUrl: './apx-chart.component.css',
})
export class ApxChartComponent {
  @Input() chart!: ApexChart;
  @Input() annotations!: ApexAnnotations;
  @Input() colors!: any[];
  @Input() dataLabels!: ApexDataLabels;
  @Input() series!: ApexAxisChartSeries | ApexNonAxisChartSeries;
  @Input() stroke!: ApexStroke;
  @Input() labels!: string[];
  @Input() legend!: ApexLegend;
  @Input() markers!: ApexMarkers;
  @Input() noData!: ApexNoData;
  @Input() fill!: ApexFill;
  @Input() tooltip!: ApexTooltip;
  @Input() plotOptions!: ApexPlotOptions;
  @Input() responsive!: ApexResponsive[];
  @Input() xaxis!: ApexXAxis;
  @Input() yaxis!: ApexYAxis | ApexYAxis[];
  @Input() forecastDataPoints!: ApexForecastDataPoints;
  @Input() grid!: ApexGrid;
  @Input() states!: ApexStates;
  @Input() title!: ApexTitleSubtitle;
  @Input() subtitle!: ApexTitleSubtitle;
  @Input() theme!: ApexTheme;

  @Input() autoUpdateSeries = true;

  @Output() chartReady = new EventEmitter();
}

apx-chart.component.html

<apx-chart
  [chart]="chart"
  [annotations]="annotations"
  [colors]="colors"
  [dataLabels]="dataLabels"
  [series]="series"
  [stroke]="stroke"
  [labels]="labels"
  [legend]="legend"
  [markers]="markers"
  [noData]="noData"
  [fill]="fill"
  [tooltip]="tooltip"
  [plotOptions]="plotOptions"
  [responsive]="responsive"
  [xaxis]="xaxis"
  [yaxis]="yaxis"
  [forecastDataPoints]="forecastDataPoints"
  [grid]="grid"
  [states]="states"
  [title]="title"
  [subtitle]="subtitle"
  [theme]="theme"
  [autoUpdateSeries]="autoUpdateSeries"
  (chartReady)="chartReady.next($event)"
></apx-chart>

Stackblitz Demo -> cd test -> npm i -> npm run start

Upvotes: 2

Related Questions