1

I understand why this error is being thrown in dev mode and why.

(for those who don't yet: ExpressionChangedAfterItHasBeenCheckedError Explained)

What i don't understand is how i should fix it in my scenario.

I have a highchart rendering stacked columns and i'm overlaying the highchart with an absolute positioned label on top of the column. I do not know how tall those stacked columns going to be prior to the rendering as the value of the series is dynamic. So i'm using the render event of highcarts to fetch the height of each point in the series and align the label accordingly.

enter image description here

Those are the chart options:

  chartOptions: Highcharts.Options = {
    chart: {
      type: 'column',
      events: {
        render: (e) => {
          if ((e.target as any).series[0]) {
            this.stackColumnHeight += ((e.target as any).series[0].points[0].shapeArgs.height as number);
            this.stackColumnHeight += ((e.target as any).series[1].points[0].shapeArgs.height as number);
          }
        },
      },
    },
    plotOptions: {
      column: {
        stacking: 'normal',
        borderWidth: 0,
        pointWidth: 60
      }
    },
    legend: {
      enabled: false
    },
    tooltip: {
      enabled: false
    },
    series: [],
    title: null,
    xAxis: {
      visible: false
    },
    yAxis: {
      visible: false
    }
  };

I'm adding the data after some API fetch has completed:

 ngAfterContentInit(): void {
    this.service.getData().subscribe(
      res => {
        // chart series
        const series = [{
          data: [res],
          type: 'column',
          color: '#C2C2C2',
          stack: 'other'
        }, {
          data: [9500],
          type: 'column',
          color: '#868686',
          stack: 'other'
        }, {
          data: [9500],
          type: 'column',
          color: '#F0A619'
        }];

        for (const s of series) {
          this.chartOptions.series.push(s as Highcharts.SeriesOptionsType);
        }

        this.updateChart = true;
      },
      err => console.log(err)
    )
  }

And this is how i've bound the position properties to the height being set in the render event.

<div style="position: absolute; z-index: 1;left: 262px;"
    [ngStyle]="{'bottom': stackColumnHeight + (somePadding) + 'px' }">
    SOME LABEL<br>
    (depending on the column height)
</div>
<highcharts-chart style="width: 60%; height: 320px; display: block;" [Highcharts]="Highcharts" [options]="chartOptions"
    [(update)]="updateChart" [oneToOne]="true"></highcharts-chart>

stackColumnHeight is 0 at the beginning and gets it value once the data fetch finishes and the chart re-renders, thus realigning the position of the label. This causes the ExpressionChangedAfterItHasBeenCheckedError.

How can i get around this?

F.H.
  • 1,456
  • 1
  • 20
  • 34
  • 1
    https://indepth.dev/posts/1001/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error a good read with some possible solutions. I might try wrapping the render `stackColumnHeight +=` expressions in a `setTimeout(() => {})` first and see if it helps, then try with ChangeDetector or even run in a ngZone. Issue really is if HighCharts is going to be OK with any of that. Without a StackBlitz its hard to test and know for sure. – Nathan Beck Dec 17 '20 at 13:52
  • Hello Nathan, i've read about the setTimeout fix and it is indeed swallowing the error. I was hoping there's a cleaner solution as i'm afraid i'll be encountering this issue frequently. – F.H. Dec 17 '20 at 14:10
  • Yea it's a tough one - the elements you need to measure are provided through a third party library, so configuring template refs and view queries is not an option. As far as I can see you're prevented from taking the correct 'angular' approach to this, so a `setTimeout()` solution isn't the worst thing in the world. Maybe there is a HighCharts config that can help with label or bar placement that doesn't require you to re-render, but that's a long shot. Best of luck! – Nathan Beck Dec 17 '20 at 21:23

1 Answers1

0

Try out below any examples and you will find fix for your problem

Example 1

@Component({
  selector: 'example',
  template: `{{stackColumnHeight}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExampleComponent { 
 stackColumnHeight = 0;

 constructor(private cdr: ChangeDetectorRef) {

  somethingMethod();
 }

 somethingMethod(e : any) {
    var height = ((e.target as any).series[0].points[0].shapeArgs.height as number) + ((e.target as any).series[1].points[0].shapeArgs.height as number);
  
    this.stackColumnHeight += height;
    this.cdr.markForCheck();  
 }
}

Example 2

  var height = ((e.target as any).series[0].points[0].shapeArgs.height as number + ((e.target as any).series[1].points[0].shapeArgs.height as number);
           
  setTimeout(() => {
   this.stackColumnHeight += height;
  });

Example 3

  let height = ((e.target as any).series[0].points[0].shapeArgs.height as number) + 
  ((e.target as any).series[1].points[0].shapeArgs.height as number);
  
  this.stackColumnHeight += height;
Yaseer
  • 506
  • 2
  • 14
  • Example 1 sadly doesn't work, example 2 as already mentioned works but seems kind of hacky and with example 3 i don't see a difference to what is in the question. – F.H. Dec 17 '20 at 16:07