1

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

ragnarswanson
  • 315
  • 3
  • 10
Felix
  • 9,248
  • 10
  • 57
  • 89
  • Could you please create an online demo with your example that I could debug it properly? Or please explain how to run the server locally. (preferably the first one) ;) – Karol Kołodziej Sep 08 '21 at 07:55
  • A little strange - it's one of the easiest problems to reproduce. But sure - you can access the site at https://aws.digitalelasticity.xyz/ . Click on Weather, and then back on Home - and you will see the problem in F12 – Felix Sep 08 '21 at 17:39

2 Answers2

0

There is one thing that isn't necessary inside your code that causes that issue.
You don't have to destroy the chart reference here. When you remove that everything works as expected.

ngOnDestroy() {
   this.chartRef.destroy()
}

Please also note that creating a second chart inside the subscribe isn't ideal. Insted use updateFlag.

this.highcharts.chart('container', this.chartOptions);

Demo: https://stackblitz.com/edit/highcharts-angular-basic-line-nxzqvy

  • Thanks. As I mentioned in my question, I used `onDestroy` and `chartRef` and callback function for test purposes only. I update github for the reference and the problem still exists. Can you elaborate on the `updateFlag`? I don't see it assigned anywhere in your code. It seems that if you change your code to retrieve data through HTTP, rather than locally, - you will get a problem as well – Felix Sep 09 '21 at 14:58
  • I see that with hardcoded `of()` the chart shows up even without what you call *second chart*; but with `http` call it doesn't. And also with hardcoded option it doesn't crash, whereas with `http` it does. – Felix Sep 09 '21 at 21:09
  • Also, I tried to find any documentation on updateFlag, without success; and since you didn't put any code in stackblitz - I am left guessing. All examples that I found relate to situation when the code changes the chart *in response* to user actions. For me it is not really updating anything - unless the chart gets generated *before* `subscribe()` is invoked (looks like it). If your recommendation is to use `updateFlag` in this context as well - please provide some guidance! Thank you – Felix Sep 09 '21 at 21:19
  • 1
    Indeed your `getData` method works well and the chart is created and destroyed properly. I'll try to debug the other one. – Karol Kołodziej Sep 13 '21 at 10:52
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:

  1. 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.

  2. 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