Angular中@ViewChild获取video元素为undefined问题求助
解决Angular中@ViewChild获取视频元素为undefined的问题
核心问题分析
你的视频元素被*ngIf="videoLoaded"包裹,只有当该值为true时才会渲染到DOM中。即使在API请求完成后调用isVideoLoaded$.next(true),Angular需要完成一轮变更检测才会实际创建视频元素,因此在subscribe回调里直接访问@ViewChild获取的元素必然是undefined。setTimeout只是通过延迟执行绕过了这个问题,但属于不可靠的临时方案。
另外,即使DOM元素存在,视频需要加载元数据后才能设置currentTime,直接操作可能也不会生效。
推荐解决方案:监听视频loadedmetadata事件
这是最可靠的方案,既保证DOM元素已渲染,又确保视频元数据加载完成,此时设置currentTime才会生效。
修改HTML代码
给<video>元素绑定loadedmetadata事件,直接在事件回调中获取视频元素:
<main class="page" *ngIf="videoLoaded"> <div class="bg-gray" *ngIf="isLoaded"> <div class="container"> <div class="video-div {{ slide.count == 0 ? 'full-width' : '' }}"> <div class="video-parent"> <video id="lecture-video" controls #video (loadedmetadata)="onVideoMetadataLoaded(video)" > <source [src]="videoUrl" /> </video> </div> </div> </div> </div> <div class="spinner-parent" *ngIf="!isLoaded"> <app-spinner></app-spinner> </div> </main>
修改TypeScript代码
移除@ViewChild,通过事件参数直接操作视频元素:
private isVideoLoaded$!: BehaviorSubject<boolean>; constructor( ... ) { this.isVideoLoaded$ = new BehaviorSubject<boolean>(false); } ngOnInit(): void { this.videoLoadedSubs = this.isVideoLoaded$.subscribe((data: boolean) => { this.videoLoaded = data; if (this.videoLoaded) { this.isPageLoaded = true; } }); this.videoApiSubs = this.videoService .getVideo(this.id) .subscribe((videoData: Video) => { this.videoData = videoData; this.videoUrl = this.videoData.video; this.lectureLoaded = true; this.isVideoLoaded$.next(true); }); } onVideoMetadataLoaded(videoElement: HTMLVideoElement) { // 视频元数据加载完成,安全设置播放起始位置 videoElement.currentTime = 500; }
备选方案:结合@ViewChild与变更检测
如果必须使用@ViewChild,可以通过强制变更检测和视图钩子确保元素存在后再操作:
TypeScript代码修改
private isVideoLoaded$!: BehaviorSubject<boolean>; @ViewChild('video', { static: false }) video!: ElementRef<HTMLVideoElement>; private hasSetCurrentTime = false; constructor( private cdr: ChangeDetectorRef, ... ) { this.isVideoLoaded$ = new BehaviorSubject<boolean>(false); } ngOnInit(): void { this.videoLoadedSubs = this.isVideoLoaded$.subscribe((data: boolean) => { this.videoLoaded = data; if (this.videoLoaded) { this.isPageLoaded = true; // 强制触发变更检测,让Angular立即渲染视频元素 this.cdr.detectChanges(); this.trySetVideoCurrentTime(); } }); this.videoApiSubs = this.videoService .getVideo(this.id) .subscribe((videoData: Video) => { this.videoData = videoData; this.videoUrl = this.videoData.video; this.lectureLoaded = true; this.isVideoLoaded$.next(true); }); } ngAfterViewChecked(): void { this.trySetVideoCurrentTime(); } private trySetVideoCurrentTime() { if (this.video && !this.hasSetCurrentTime) { const videoElement = this.video.nativeElement; // 检查视频是否已加载元数据(readyState >=2 对应HAVE_METADATA) if (videoElement.readyState >= 2) { videoElement.currentTime = 500; this.hasSetCurrentTime = true; } else { // 监听一次loadedmetadata事件 videoElement.addEventListener('loadedmetadata', () => { videoElement.currentTime = 500; this.hasSetCurrentTime = true; }, { once: true }); } } }
内容的提问来源于stack exchange,提问作者devZ
相关产品推荐
相关产品推荐

