Angular中调用Timer服务 second_left为null 倒计时无法正常显示
问题原因
- 异步执行时序问题:你调用的
setInterval是异步宏任务,传入的回调要等至少1000ms才会第一次执行。你在构造函数里刚调用start_countdown()就立刻读取second_left,此时回调还没触发,属性根本没被赋值,自然拿到空值。 - 缺少响应式通知机制:就算等第一次回调执行给
second_left赋了值,你用的是普通属性,组件无法感知到属性每秒的变化,模板没法自动刷新显示最新的倒计时数值。 - 原有逻辑还有两个小bug:一是初始值要等1秒才会渲染,不符合打开就显示
05:00的要求;二是倒计时到0后没有清除定时器,会一直空跑占用资源。
修复实现步骤
第一步:改造倒计时服务
用Angular自带的RxJS响应式能力改造服务,让倒计时数值变化可以被组件订阅到,修复时序和定时器泄漏问题:
import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class TimerUtilService { private readonly initial_module_timer_value = 300; // 用BehaviorSubject存倒计时值,初始直接设为05:00,订阅者可以立刻拿到初始值 private readonly _secondLeft = new BehaviorSubject<string>('05:00'); // 暴露只读的Observable给外部,防止外部随意修改内部状态 readonly secondLeft$ = this._secondLeft.asObservable(); private module_finish_interval: any; time_limit_over = false; start_countdown() { // 先清掉可能存在的旧定时器,避免重复启动 this.end_countdown(); this.time_limit_over = false; this.startTimer(this.initial_module_timer_value); } private startTimer(duration: number) { let timer = duration; // 启动立刻推送一次初始值,不用等1秒间隔 this.updateTimeDisplay(timer); this.module_finish_interval = setInterval(() => { timer--; if (timer < 0) { this.time_limit_over = true; this.end_countdown(); console.log('completed'); return; } this.updateTimeDisplay(timer); }, 1000); } private updateTimeDisplay(totalSeconds: number) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; const displayValue = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; this._secondLeft.next(displayValue); } end_countdown() { if (this.module_finish_interval) { clearInterval(this.module_finish_interval); this.module_finish_interval = null; } } }
第二步:改造组件调用逻辑
不要在构造函数里写业务初始化逻辑,移到ngOnInit生命周期里,订阅服务的倒计时数据流,同时记得在组件销毁时取消订阅避免内存泄漏:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { TimerUtilService } from '../../../shared/timer-util.service'; import { ActivatedRoute, Router } from '@angular/router'; import { ElementRef } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Subject, takeUntil } from 'rxjs'; export class TestComponent implements OnInit, OnDestroy { // 组件内用来绑定模板的倒计时显示值 countdownDisplay = '05:00'; // 用来取消订阅的销毁通知 private destroy$ = new Subject<void>(); constructor( private route: ActivatedRoute, private elem: ElementRef, private router: Router, private http: HttpClient, private timerService: TimerUtilService ) {} ngOnInit() { this.timerService.start_countdown(); // 订阅倒计时变化,自动更新显示值 this.timerService.secondLeft$ .pipe(takeUntil(this.destroy$)) .subscribe(displayVal => { this.countdownDisplay = displayVal; console.log(displayVal); // 这里每秒都会打印最新的倒计时值 }); } ngOnDestroy() { // 组件销毁时取消订阅、清定时器 this.destroy$.next(); this.destroy$.complete(); this.timerService.end_countdown(); } }
第三步:模板绑定显示
在组件的html模板里直接绑定属性即可,会自动每秒更新:
<div class="countdown">{{ countdownDisplay }}</div>
内容的提问来源于stack exchange,提问作者user3653474
相关产品推荐
相关产品推荐

