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
(i.e. 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
);
}
}