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
相关产品推荐
相关产品推荐

