4

I am trying to make a circle board in Angular2. For the example I want to make 10 circles but in reality this number can change. I want to calculate the radius of each circle, so it would be dynamic and not static. See the picture for exampleenter image description here

This is my code

@Component({
    selector:"circle"
    template: `
  <svg>
    <circle *ngFor='#item of forLength #i=index #l=last #e=even'
            cx="50%" cy="50%" [style.r]="calculateRadius()" stroke="black" stroke-width="5" fill="white"></circle>
  <svg/>  
    `
})

export class CircleComponent{
    public maxRadius:number=25;
    public totalRounds:number=10;
    public x:number=30;

    public calculateRadius():number{
        var distanceBetweenCircles=this.maxRadius/(this.totalRounds-1);
        this.x-= distanceBetweenCircles;
        return this.x;
    }
}

But I get the following error:

calculateRadius() in CircleComponent@7:30' has changed after it was checked. 
    Previous value: '-7.500000000000007'. 
    Current value: '-36.66666666666668' in [calculateRadius() in CircleComponent@7:30]

Is there maybe a better way of writing this for loop with *ngFor instead of writing this in a separate method?

Claudiu Matei
  • 4,091
  • 3
  • 19
  • 33
  • Where are you calling `calculateRadius()` in your template? Where do you set the SVG `r` attribute in your template? – Mark Rajcok Feb 29 '16 at 22:38
  • @MarkRajcok Sorry I have written a mistake in the code, I have now updated the code. I use calculateRadius() in the tag – Claudiu Matei Feb 29 '16 at 22:45

2 Answers2

15

In development mode (the default), change detection is run twice to ensure that model changes have stabilized. This means that the ngFor loop is evaluated twice. Hence property x will continue to be decremented the second time change detection runs. Other activity in your app will also cause change detection to run, and x will continue to be decremented. Therefore, you must write all view functions, like calculateRadius(), assuming they will be executed many times. E.g.:

public calculateRadius(i):number{
    return this.x - i*this.distanceBetweenCircles;
}

The Template Syntax dev guide mentions this when it describes idempotent expressions.

This will also solve the value has changed after it was checked problem.

You also need to bind SVG attribute r using this syntax: [attr.r]="...", not [style.r]="...".

Plunker

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Thank you! There could not be a better explanation. It works now! – Claudiu Matei Feb 29 '16 at 23:51
  • 1
    Since Angular2 RC3, you can solve this situation by changing the change detection stratedy: import {ChangeDetectionStrategy} from '@angular/core'; @Component({changeDetection: ChangeDetectionStrategy.OnPush}) – Becario Senior Jul 26 '16 at 14:20
  • 1
    @BecarioSenior, I would not recommend using OnPush as a solution to this issue. OnPush should be used with components that only depend on their input properties and there are no input properties here. Even if we were to add an input property, a component method shouldn't modify the value the input property in this manner (in this scenario, such an input property should be read only, in my opinion). Also, expressions used in templates should be idempotent. – Mark Rajcok Jul 26 '16 at 21:49
0

“value has changed after it was checked”

The return from any value must be deterministic. Means if I call value (might be calculateRadius()) with the same inputs it should give the same output.

So pass index into calculateRadius (or whatever value is giving that error)

basarat
  • 261,912
  • 58
  • 460
  • 511