I can reproduce the problem in a very simple Angular-Highcharts application. When I retrieve the data from the server, and then leave the page with Highcharts chart, I am getting this error:
Cannot read property 'forExport' of undefined at a.destroy (highcharts.js:394) at HighchartsChartComponent.ngOnDestroy (highcharts-angular.js:44)
Not surprisingly, HighchartsChartComponent.ngOnDestroy
is
ngOnDestroy() {
if (this.chart) { // #56
this.chart.destroy();
this.chart = null;
}
}
Here is the minimal example:
export class WeatherForecastComponent implements OnInit {
highcharts: typeof Highcharts = Highcharts;
chartOptions: Highcharts.Options = { ... };
constructor(private http: HttpClient) { }
ngOnInit(): void {
this.http.get<any[]>(`http://localhost:57432/WeatherForecast`, {
headers: new HttpHeaders({
"Content-Type": "application/json"
})})
.subscribe(result => {
let categories: string[] = [];
let dataSeries: number[] = [];
for (let i = 0; i < result.length; i++) {
categories.push(result[i]['location']);
dataSeries.push(result[i]['temperatureC']);
}
this.chartOptions.xAxis = {
categories: categories
};
this.chartOptions.series = [{
type: "column",
name: "Cities",
data: dataSeries
}];
this.highcharts.chart('container', this.chartOptions);
}, error => {
console.log(error);
});
}
}
I saw a number of similar issues both on Stack Overflow and on Github. I understand that the root cause is that it's trying delete the chart that is already deleted. But all of them are deleting (or attempting to delete) the charts in application code.
I tried to delete the chart in ngOnDestroy()
, or to unsubscribe - but no difference.
Here is the repo, including Server-side code.
Note, that the problem doesn't happen when it is a "hardcoded" chart.
Angular: 12.2; Highchars 9.2.2; Highcharts-Angular: 2.10.0
There are two main things that don't work. But before I could explain let's look at the code responsible for creating the chart in the wrapper.
ngOnChanges(changes) {
const update = changes.update && changes.update.currentValue;
if (changes.options || update) {
this.wrappedUpdateOrCreateChart();
if (update) {
this.updateChange.emit(false); // clear the flag after update
}
}
}
wrappedUpdateOrCreateChart() {
if (this.runOutsideAngular) {
this._zone.runOutsideAngular(() => {
this.updateOrCreateChart();
});
}
else {
this.updateOrCreateChart();
}
}
updateOrCreateChart() {
if (this.chart && this.chart.update) {
this.chart.update(this.options, true, this.oneToOne || false);
}
else {
this.chart = this.Highcharts[this.constructorType || 'chart'](this.el.nativeElement, this.options, this.callbackFunction || null);
// emit chart instance on init
this.chartInstance.emit(this.chart);
}
}
As you can see, everything is happening in the ngOnChanges
hook. If the change is detected the series of methods are fired to finally update the chart.
And now the things which don't work:
In the case when there is any asynchronous code happening in the subscribe
(like for example HTTP request - on my example mocked by setTimeout
) you have to manually set to wrapper that is should be updated by setting the updateFlag
. Because Angular doesn't detect changes in some properties in the chartOptions
.
If you split assigning properties to the chartOptions
like in the code snippet below set the oneToOne
flag to true
this.chartOptions.xAxis = {
categories: categories
};
this.chartOptions.series = [{
type: "column",
name: "Cities",
data: dataSeries
}];
Docs: https://github.com/highcharts/highcharts-angular#options-details
Demo: https://stackblitz.com/edit/highcharts-angular-basic-line-vwrrmx