Angular child component doesnt recognize input changes

Viewed 391

Hello I use parent child communication. At parent I have array which represents graph datas. Demo

My problem is that when i change one item in array i doesn't fire child component appchanges. In demo I created example which doesn't work. When I click update I want to update child component graph.

My child component

import { Component, OnInit, Input, OnChanges } from "@angular/core";
import * as Highcharts from "highcharts";
import { ChartService } from "./chart.service";

@Component({
  selector: "hello",
  template: `
    <highcharts-chart
      [Highcharts]="Highcharts"
      [options]="options"
      [oneToOne]="true"
      [update]="updateFromInput"
      style="width: calc(100% ); height: calc(100% - 17px); display: block;margin-top:15px;overflow: auto !important;"
    >
    </highcharts-chart>
  `,
  styles: [
    `
      h1 {
        font-family: Lato;
      }
    `
  ]
})
export class HelloComponent implements OnInit, OnChanges {
  innerHeight: any;
  innerWidth: any;

  @Input() data: any;

  Highcharts: typeof Highcharts = Highcharts;
  updateFromInput = false;
  options: any;

  constructor(private chart_service: ChartService) {
    this.innerHeight = window.screen.height;
    this.innerWidth = window.screen.width;
  }

  onResize(event) {
    this.innerWidth = event.target.innerWidth;
    this.update();
  }

  ngOnInit() {
    this.update();
  }

  ngOnChanges() {
    console.log(this.data);
    console.log("değişti");
    this.update();
  }

  update() {
    var series = this.chart_service.groupBy(
      this.data.LINE.DATA,
      "GRUP",
      "line"
    );
    var categories = this.chart_service.ArrNoDupe(
      this.data.LINE.DATA.map(x => x.NAME)
    );
    let isLegend = series.length > 1 ? true : false;
    var size = this.innerWidth / (12 / this.data.SIZE) - 30;
    if (this.innerWidth <= 992) {
      size = this.innerWidth - 30;
    }
    this.options = {
      title: { enabled: false, text: "" },
      credits: { enabled: false },
      legend: { enabled: true },
      tooltip: { hideDelay: 0, outside: true, shared: true },
      plotOptions: {
        line: { dataLabels: { enabled: !isLegend }, enableMouseTracking: true }
      },
      yAxis: { title: { enabled: false } },
      xAxis: { categories: categories },
      series: series
    };
  }
}
5 Answers

In your app.component.ts:

filter(id) {
    this.reports.filter(x => x.ID == id)[0] = {
      ID: 3233.0,
    ...
   }
}

Array.prototype.filter does not mutate the given array, but returns a new array with the filtered properties. You must reassign this value to make the Angular change detection detect this new array:

filter(id) {
    this.reports = this.reports.filter(x => x.ID == id)[0] = {
      ID: 3233.0,
    ...
   }
}

It looks like there are three mistakes in your code:

  • Your @Input() getter/setter are switched
@Input() set data(data: any) {
  this._param = data;
}

get data(): any {
  return this._param;
}
  • Your filter actually does not filter anything (at least as far as I see from the data)
  • Your *ngFor needs to be notified, that your data has changed - for that you could implement the ngOnChanges lifecycle hook and assign the latest value from the SimpleChanges of your reports to the member variable reports

Exactly as @code-gorilla mentioned, it is not working because you are not mutating the reports array so the changes are not detected = your chart is not updated.

After modifying your code so it will mutate the reports I was able to make it working, you can find more details in the demo I attached below.

  filter(id) {
    const updatedReport = (this.reports.filter(
      x => x.CHART_ITEM_ID == id
    )[0] = { ... } 
    });

    this.reports[0] = updatedReport;
  }

Live demo:
https://stackblitz.com/edit/angular-7yr5qg?file=src%2Fapp%2Fapp.component.ts

In general, OnChanges is fired when the data object source has changed, but not if the data has been mutated. In order for an array to cause OnChanges to be called, it must be updated in an immutable fashion. In this article example, slice is a immutable function - meaning it does not change the original array, whereas splice is a mutable function- meaning it changes the original array.

So in your example, in order for onChanges to be called, you would need to use an immutable function like map instead of filter.

In your specific case, it looks like you do not really want to use filter, since you just need the first element that matches the predicate. You should really use find instead. have a separate property of report, then have your function call report = this.reports.find(x => x.CHART_ITEM_ID == id) , and bind report to the child

After helpful answer above I assing firstly index to variable then updated with using index like below code Demo

 let index=this.reports.findIndex( x => x.CHART_ITEM_ID == id ); 
  this.reports[index]= ...
Related