简体   繁体   中英

angular2 how do you render a timer Component inside `ngFor`

I want to render a visual countdown timer. I'm using this component, https://github.com/crisbeto/angular-svg-round-progressbar , which relies on SimpleChange to deliver changes.current.previousValue & changes.current.currentValue .

Here is the template code

<ion-card  class="timer"
  *ngFor="let snapshot of timers | snapshot"
> 
  <ion-card-content>
    <round-progress 
      [current]="snapshot.remaining" 
      [max]="snapshot.duration"
      [rounded]="true"
      [responsive]="true"
      [duration]="800"
      >
    </round-progress>
</ion-card>

I'm using this code to trigger angular2 change detection

  this._tickIntervalHandler = setInterval( ()=>{
      // run change detection
      return;
    }
    // interval = 1000ms
    , interval
  )

updated (After much testing, I've discovered that the problem isn't the precision of the time I am rendering, and this question has been changed to reflect that.)

The problem is that ngFor is called multiple times inside 1 change detection loop. Regardless of my tick interval or the precision of snapshot.remaining (ie seconds or tenths of seconds) if snapshot.remaining happens to change on a subsequent call to ngFor during change detection, I get an exception:

Expression has changed after it was checked

If I render only a single timer without using ngFor then change detection works fine--even for intervals of 10ms .

How can I render multiple timers on a page, presumably using ngFor , without triggering this exception?

Solution

After a bit of testing, it seems the problem is using a SnapshotPipe with ngFor to capture the snapshot of the Timer data. What finally worked is to take the snapshot of the Timer data in the View Component. As mentioned in the answer below, this uses a pull method to get changes, instead of a push method.

    // timers.ts
    export class TimerPage {
        // use with ngFor
        snapshots: Array<any> = [];

        constructor(timerSvc: TimerSvc){
            let self = this;
            timerSvc.setIntervalCallback = function(){
                self.snapshots = self.timers.map( (timer)=>timer.getSnapshot()  );
            }
        }

    }


    // timer.html
    <ion-card  class="timer"  *ngFor="let snapshot of snapshots"> 
    <ion-card-content>
        <round-progress 
        [current]="snapshot.remaining" 
        [max]="snapshot.duration"
        [rounded]="true"
        [responsive]="true"
        [duration]="800"
        >
        </round-progress>
    </ion-card>



    // TimerSvc can start the tickInterval
    export class TimerSvc {
        _tickIntervalCallback: ()=>void;
        _tickIntervalHandler: number;

        setIntervalCallback( cb: ()=>void) {
            this._tickIntervalCallback = cb;
        }

        startTicking(interval:number=100){
            this._tickIntervalHandler = setInterval( 
                this._tickIntervalCallback
                , interval
            );
        }
    }

The first suspicious thing is toJSON (it has wrong name or returns string or converts string to array), which objects are contained in timers array?

During change detection for loop might be evaluated multiple times so it should not generate new objects. Also toJSON pipe should be marked as pure (see Pipe decorator options). Also it is better to provide trackBy function for ngFor . In general it is better in this case to update class field instead of using pipe.


public snapshots:any[] = [];

private timers:any[] = [];

setInterval(()=>{
    this.snapshots = this.updateSnapshots(this.timers);
}, 100);

<ion-card  class="timer"
  *ngFor="let snapshot of snapshots"
> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM