10

I have an Angular 2 component that uses *ngFor to render a nested array of numbers.

@Component({
  selector: 'app',
  template: `
    <div *ngFor="let row in data">
      <div *ngFor="let curve in row">
        <chart [data]="curve">
      </div>
    </div>
  `,
  directives: [Chart],
})
export default class App {
  data: number[][][];
}

When I change data, Angular replaces the <chart> elements, even if the new array has the same dimensions. I'd like it only to update the properties of the charts but keep the elements (so that I can animate the data changes).

It's understandable that Angular replaces the elements since the new array is a new object reference. But how can I circumvent this?

danijar
  • 32,406
  • 45
  • 166
  • 297
  • Could you use [ngForTrackBy](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ngfortrackby)? – Michael Jul 03 '16 at 03:09
  • @Michael That's exactly what I was looking for: `trackByIndex(index: number, data: any) { return index; }`. Could you make this an answer, please? – danijar Jul 03 '16 at 11:55
  • 1
    +1 This question has a disturbingly view amounts of views and upvotes. ReactJS, for example, throws a hissy if you forget to give your list items a 'key' property so that the list can be economically re-rendered. – Stephen Paul Feb 01 '17 at 07:14

2 Answers2

7

Using ngForTrackBy should help you here.

The following function should update the element properties whilst keeping the elements in the DOM.

trackByIndex(index: number, data: any) { return index; }
Michael
  • 5,994
  • 7
  • 44
  • 56
2

Working Plunker

Flat Data Structure

You can separate the metadata about your charts (title, id, etc) from the actual data points that will be changing and animated.

The *ngFor loop would use the metadata array and each chart component would have an @Input() that would pull from a separate object of data points.

// Metadata
this.charts = [
  {
    id: 1,
    title: 'Chart Title 1',
  },
  {
    id: 2,
    title: 'Chart Title 2',
  }
];

// Data Points
this.chartData = {
  1: [87, 95, 121, 20, 60, 131, 10, 139, 128, 99],
  2: [56, 107, 107, 144, 43, 7, 67, 35, 141, 62]
}

Template

<div *ngFor="let chart of charts">
  <app-chart [inputMeta]="chart" [inputData]="chartData[chart.id]"></app-chart>
</div>

Then in your chart component you can use the ngOnChanges() lifecycle hook to update the chart when you update your data points.

Community
  • 1
  • 1
adriancarriger
  • 2,970
  • 3
  • 19
  • 26