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.