3

Can I have components inside ngFor directive, in such a way, that component's constructor doesn't have to be called every time, when data changes?

Plunker example: http://plnkr.co/edit/6A6Aj8yeFqNd28Nl8cyS?p=preview

I just want to update data, not re-create component. Open console to see better what I mean.

AppComponent:

@Component({
  selector: 'my-app',
  template: `
    <div>
      <div *ngFor="let value of sourceData">
        <fizz [sampleInputValue]="value"></fizz>
      </div>
    </div>
  `,
})
export class App {

  sourceData: number[] = [];

  ngOnInit() {
    setInterval(() => {
      let newArrayOfRandomNumbers: number[] = [
        Math.floor(Math.random() * 60) + 1,
        Math.floor(Math.random() * 60) + 1,
        Math.floor(Math.random() * 60) + 1,
        Math.floor(Math.random() * 60) + 1,
        Math.floor(Math.random() * 60) + 1
      ]

      newArrayOfRandomNumbers.forEach((randomNumber, index) => {
        this.sourceData[index]= newArrayOfRandomNumbers[index];
      });
    }, 500);
  }
}

FizzComponent:

@Component({
  selector: 'fizz',
  template: `
    {{sampleInputValue}}  
  `
})
export class FizzComponent {

  @Input()sampleInputValue: number;

  constructor() {
    console.log("I dont want to be called, I just want the data to be updated")
  }
}
bartosz.baczek
  • 1,567
  • 1
  • 17
  • 32
  • What else is in your constructor? Can you elaborate more why you don't want to call the constructor. By the way, you can just delete the constructor because you are passing an @Input() anyway and it is already initialized when you pass it from AppComponent – brijmcq Aug 29 '17 at 14:08
  • I am using component that shows gauge. I want gauge's hand to move fluently from one value to another. If component is recreated every time, it always starts from zero. That's why I don't want to recreate component every time. – bartosz.baczek Aug 30 '17 at 07:55
  • do you only need to display 5 values? I have alternative solution by not using the ngFor if that's what you want – brijmcq Aug 30 '17 at 08:06
  • If you would be so kind, than I'd love to hear that. Sorry for not being precise enough in my question. Please note, that 5 values is just sample - there can by any number of values to display. – bartosz.baczek Aug 30 '17 at 08:22
  • Hard-coding the sourcedata like sourcedata[0] in your template works and does not call the constructor always once it is created. Otherwise, @maxim koretsyki answer is the only option you have. – brijmcq Aug 30 '17 at 09:16

2 Answers2

2

The problem is that Angular uses default trackBy function that compares by identity. If there is a mismatch it recreates the component and calls it's constructor as a consequence.

You could leverage that and pass values as object with numeric properties to ngFor:

  sourceData = [{},{},{},{},{}];

  ngOnInit() {
    setInterval(() => {
      let newArrayOfRandomNumbers: number[] = [
        Math.floor(Math.random() * 60) + 1,
        Math.floor(Math.random() * 60) + 1,
        Math.floor(Math.random() * 60) + 1,
        Math.floor(Math.random() * 60) + 1,
        Math.floor(Math.random() * 60) + 1
      ]

      newArrayOfRandomNumbers.forEach((randomNumber, index) => {
        this.sourceData[index].v= newArrayOfRandomNumbers[index];
      });
    }, 500);

This will not be re-creating components. See this plunker.

Also see this answer to understand more about ngFor trackBy.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
0

No, that's not possible.

Every time a component will be rendered, a new instance will be created. If you want to execute a logic just one time, move it from the constructor to a service.

Markus Kollers
  • 3,508
  • 1
  • 13
  • 17