【问题标题】:angular2 how do you render a timer Component inside `ngFor`angular2你如何在`ngFor`中渲染一个计时器组件
【发布时间】:2017-06-27 12:17:01
【问题描述】:

我想呈现一个视觉倒数计时器。我正在使用这个组件,https://github.com/crisbeto/angular-svg-round-progressbar,它依赖于SimpleChange 来传递changes.current.previousValuechanges.current.currentValue

这是模板代码

<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>

我正在使用此代码来触发 angular2 更改检测

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

更新 (经过多次测试,我发现问题不在于我渲染的时间精度,这个问题已经改变以反映这一点。)

问题是ngFor 在 1 个更改检测循环内被多次调用。无论我的刻度间隔或snapshot.remaining 的精度如何(即秒或十分之一秒),如果在更改检测期间snapshot.remaining 碰巧在随后调用ngFor 时发生更改,我都会遇到异常:

Expression has changed after it was checked

如果我只渲染一个计时器而不使用ngFor,那么更改检测工作正常——即使间隔为10ms

如何在一个页面上呈现多个计时器,大概使用ngFor,而不触发此异常?

解决方案

经过一番测试,似乎问题在于使用SnapshotPipengFor 来捕获定时器数据的快照。最终起作用的是在 View Component 中获取 Timer 数据的snapshot。正如下面的答案中提到的,这使用pull 方法来获取更改,而不是push 方法。

    // 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
            );
        }
    }

【问题讨论】:

    标签: angular ionic2 zonejs


    【解决方案1】:

    首先可疑的是toJSON(名称错误或返回string或将字符串转换为数组),timers数组中包含哪些对象?

    在更改检测期间,可能会多次评估循环,因此它不应生成新对象。此外,toJSON 管道应标记为 pure(请参阅管道装饰器选项)。此外,最好为ngFor 提供trackBy 函数。一般来说,在这种情况下更新类字段而不是使用管道会更好。


    public snapshots:any[] = [];
    
    private timers:any[] = [];
    
    setInterval(()=>{
        this.snapshots = this.updateSnapshots(this.timers);
    }, 100);
    
    <ion-card  class="timer"
      *ngFor="let snapshot of snapshots"
    > 
    

    【讨论】:

    • toJSON 可以很容易地重命名为snapshot,它在几秒钟内返回一个带有snapshot.remaining 的简单对象,具有不同的精度。我添加了trackByFn,但这并没有解决问题。 pure:true 没有帮助,因为 SimpleChange 需要对象引用保持 SAME 才能捕获等于 snapshot.remainingchanges.current.previousValue。问题是计时器在ngFor 循环期间在MS 中滴答作响。当我输入这个时,我在想一个黑客可能是去抖动snaphshot...
    • 我的意思是throttle,我尝试了throttle 不同的值,但没有运气。此外,ngFor 循环有多快并不重要,因为正如您所提到的,更改检测循环似乎多次调用ngFor。如果计时值发生变化,则会引发错误。即使精度为 1 秒也是如此。
    • 如果我从ngFor 中取出计时器,我可以每 10 毫秒渲染一次计时器而不会出错。更新问题以反映这一事实
    • 我认为在这种情况下你应该在模板所有者组件中运行setInterval,并在回调中更新快照数组,换句话说,推送新值而不是让角度随机拉取新值。跨度>
    • 行得通!!!它没有被完全封装,但它似乎确实有效。我可以使用ngFor,并且仍然可以通过这种方法渲染百分之一秒。我将稍等片刻,看看是否有另一种方法可以更好地用 Timer 封装——但如果没有,我会接受你的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-14
    • 2013-01-20
    • 1970-01-01
    • 2019-05-14
    • 1970-01-01
    • 2017-02-09
    相关资源
    最近更新 更多