You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular低开销倒计时实现:管道封装+超时通知服务

问题描述

在Angular应用中实现页面倒计时功能,倒计时从指定的日期时间开始计算每秒更新一次,上线后发现谷歌Chrome浏览器CPU占用过高,怀疑和moment.js库的频繁调用有关,想找到效率更高的实现方式。

原实现代码

业务逻辑:

calculateTimeRemaining(endDate: Date, timeZone: string, eventId: number) {
  setInterval(() => {
    const element = document.getElementById("event-" + eventId);
    if (element) {
      const currentDateUtc = moment().utc();
      const endDateUtc = moment(endDate).utc(true);
      endDateUtc.tz(timeZone);
      const dif = moment.duration(endDateUtc.diff(currentDateUtc));
      const hours = dif.hours();
      const minutes = dif.minutes();
      const seconds = dif.seconds();
      if (hours < 1) {
        element.innerHTML = minutes + ' m ' + seconds + ' s';
      } else {
        element.innerHTML = hours + ' h ' + minutes + ' m ' + seconds + ' s';
      }
    }
  }, 1000);
}

模板调用:

<span id="event-{{event.id}}" class="float-right yb-color">{{calculateTimeRemaining(event.datePendingUtc, event.yogabandTimeZoneId, event.id)}}</span>

优化解决方案

采用Angular自定义管道 + RxJS定时器实现,完全移除moment依赖,计时到期后自动停止订阅,从根源降低性能消耗,同时额外实现了过期事件通知服务方便业务层后续处理。

1. 自定义倒计时管道代码

import { Pipe, PipeTransform } from '@angular/core';
import { Observable, of, timer } from 'rxjs';
import { map, takeWhile } from 'rxjs/operators';
import { EventService } from 'src/app/core/services/event.service';

@Pipe({
  name: 'timeRemaining'
})
export class TimeRemainingPipe implements PipeTransform {
  eventId: number;
  expired = false;

  constructor(private eventService: EventService) {}

  /**
   * @param futureDate    合法的UTC日期时间格式,例如 YYYY-MM-DDTHH:mm:ss.msz、2021-10-06T17:27:10.740z
   */
   public transform(futureDateUtc: string, eventId: number): Observable<string> {
    
    this.eventId = eventId;
    // 提前判断时间是否已过期,避免无意义创建定时器
    if (!futureDateUtc || this.getMsDiff(futureDateUtc) < 0) {
      console.info('管道 - 事件已过期:' + eventId);
      return of('已过期');
    }
    
    return timer(0, 1000).pipe(
        takeWhile(() => !this.expired),
        map(() => {
            return this.msToTime(this.getMsDiff(futureDateUtc));
        })
    );
  }

  /**
   * 计算未来时间和当前时间的毫秒差
   * @private
   * @param   futureDateUtc: string
   * @returns number  剩余毫秒数
   */
   private getMsDiff = (futureDate: string): number => (+(new Date(futureDate + 'Z')) - Date.now());


   /**
    * 将毫秒转换为 HH:mm:ss 格式的时间字符串
    * @private
    * @param msRemaining
    * @returns 时间到了返回「已过期」,否则返回格式化后的时间字符串
    */
   private msToTime(msRemaining: number): string | null {
       if (msRemaining < 0) {
           console.info('管道 - 无剩余时间:', msRemaining);
           this.expired = true;
           this.eventService.expired(this.eventId);
           return '已过期';
       }

       let seconds: string | number = Math.floor((msRemaining / 1000) % 60),
           minutes: string | number = Math.floor((msRemaining / (1000 * 60)) % 60),
           hours: string | number = Math.floor((msRemaining / (1000 * 60 * 60)) % 24);

       // 小于10的数字补前导0
       seconds = (seconds < 10) ? '0' + seconds : seconds;
       minutes = (minutes < 10) ? '0' + minutes : minutes;
       hours = (hours < 10) ? '0' + hours : hours;

       return `${hours}:${minutes}:${seconds}`;
   }
}

2. 模板调用代码

<span class="float-right yb-color">{{event.dateUtc | timeRemaining: event.id | async}}</span>

3. 事件过期通知服务代码

import { Subject, Observable } from 'rxjs';

export class EventService {
  private subject = new Subject <any> ();

  expired(eventId: number) {
    this.subject.next(eventId);
  }

  expiredEvents(): Observable <any> {
    return this.subject.asObservable();
  }
}

4. 组件内监听过期事件

// 组件内调用示例
this.eventService.expiredEvents().subscribe(eventId => {
  console.info('服务 - 事件已过期:', eventId);
  // 此处执行事件过期后的业务逻辑
});

内容的提问来源于stack exchange,提问作者chuckd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 05:54:01