最近正写个小项目复习 Angular,在 DOM 对象的获取上遇到了个离奇的问题,目前虽然已经解决,但原理还没完全搞懂,先记录下来。
问题
Angular 组件间通信会用到 @Input()、@ViewChild() 等装饰器,正常的 Angular 组件通信不出意外都是没问题的,不再多讲。
由于临时想到记录下来怕以后忘记,而问题已经解决,当时没有截图,所以只是“简单地”用文字记录下如果要获取原生的 DOM 对象,随便查查就能看到类似下面的写法(伪代码)
1
2
3
4
5
6
7
8
9
10
11
12
13
| import { AfterViewInit, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-file-preview',
templateUrl: './file-preview.component.html',
styleUrls: ['./file-preview.component.scss']
})
export class FilePreviewComponent implements AfterViewInit {
@ViewChild("imageViewer") imageViewer!: ElementRef;
ngAfterViewInit(): void {
this.imageViewer.nativeElement.src = "https://example.com/xxx.jpg";
}
|
简单的使用貌似没什么问题,如果遇到获取的对象是 undefined 在视图初始化之后再处理对象就行,也就是使用 ngAfterViewInit 代替 onInit。
而我的需求稍微麻烦点:
- 根据文件对象判断文件类型,得到不同的组件初始化方法
- 根据文件对象获取远端的文件地址,得到可观察对象
- 得到可观察对象后,根据 1 得到的初始化方法对组件初始化
前两步正常运行,第三步出现 undefined,报错的部分相关代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| import {...} from ...;
@Component({
...
})
export class FilePreviewComponent implements OnInit, AfterViewInit {
@Input() file!: File;
@ViewChild("mediaViewer") mediaViewer!: ElementRef;
fileUrl!: string;
groupId = environment.groupId;
isVideo!: boolean;
isAudio!: boolean;
isImage!: boolean;
constructor(private fileService: FileService) { }
ngAfterViewInit(): void {
console.log(this.mediaViewer.nativeElement); // => [object Object]
this.isVideo = ["mp4", "mkv", "flv", "wmv", "avi"]
.indexOf(this.file.getFileType()) != -1;
this.isAudio = ["mp3", "wav", "flac", "m4a", "aac", "pcm"]
.indexOf(this.file.getFileType()) != -1;
this.isImage = ["png", "jpg", "jpeg", "gif"]
.indexOf(this.file.getFileType()) != -1;
const mediaInitializer: any = null;
if (this.isVideo) {
mediaInitializer = this.initVideoPlayer;
} else if (this.isAudio) {
mediaInitializer = this.initAudioPlayer;
} else if (this.isImage) {
mediaInitializer = this.initImageViewer;
}
if (mediaInitializer) {
this.fileService.getGroupFileUrl(this.groupId, this.file.fileId, this.file.busid)
.subscribe(resp => {
this.fileUrl = resp.data.url;
mediaInitializer();
})
}
}
ngOnInit(): void { }
/**
* 视频播放器初始化
*
* @param fileUrl
*/
initVideoPlayer(fileUrl?: string): void {
console.log(this.mediaViewer.nativeElement); // => cannot read property 'mediaViewer' of undefined.
}
/**
* 音频播放器初始化
*
* @param fileUrl
*/
initAudioPlayer(fileUrl?: string): void {
...
}
/**
* 图片预览初始化
*
* @param fileUrl
*/
initImageViewer(fileUrl?: string): void {
...
}
}
|
分析
根据上面在 ngAfterViewInit 和 initVideoPlayer 分别打印的结果来分析,
视图初始化后 this.mediaViewer 已经能获取到了,那么在视图初始化之后才调用的方法中,
理论上 this.mediaViewer 应该也是存在的,然而却获取报错。
如果网上查一查,大多数说的都是同样的解决办法,他们的问题都是一样的:DOM 对象初始化之前就使用了变量。
所以只需要换个事件监听就好。
但我这个问题貌似有点不同,仔细看一下报错: cannot read property 'mediaViewer' of undefined. 凭记忆敲的,可能和原来的 message 有点出入
报错并不是 this.mediaViewer 是 undefined,而是说无法从 undefined 获取名为 mediaViewer 的属性……
明明是从 this 获取的自定义属性,这么说的话,initVideoPlayer 中 this 发生了什么奇怪的变化?
试着打印一下(代码略),果然前者还是对象,后者就成了 undefined。
更多的,由于本人 javascript 经验不多,无法做出更确切的分析。
猜测或许是因为这段代码的赋值,导致获取到的方法由 method 转换成了 function 导致?
1
2
3
4
5
6
7
8
9
| const mediaInitializer: any = null;
if (this.isVideo) {
mediaInitializer = this.initVideoPlayer;
} else if (this.isAudio) {
mediaInitializer = this.initAudioPlayer;
} else if (this.isImage) {
mediaInitializer = this.initImageViewer;
}
|
解决
顺着上面的分析,将 mediaInitializer 作为属性赋值,而不是局部变量,以此解决问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
| import {...} from ...;
@Component({
...
})
export class FilePreviewComponent implements OnInit, AfterViewInit {
@Input() file!: File;
@ViewChild("mediaViewer") mediaViewer!: ElementRef;
fileUrl!: string;
groupId = environment.groupId;
isVideo!: boolean;
isAudio!: boolean;
isImage!: boolean;
mediaInitializer: any = null;
constructor(private fileService: FileService) { }
ngAfterViewInit(): void {
console.log(this.mediaViewer.nativeElement); // => [object Object]
if (this.mediaInitializer) {
this.fileService.getGroupFileUrl(this.groupId, this.file.fileId, this.file.busid)
.subscribe(resp => {
this.fileUrl = resp.data.url;
this.mediaInitializer();
})
}
}
ngOnInit(): void {
this.isVideo = ["mp4", "mkv", "flv", "wmv", "avi"]
.indexOf(this.file.getFileType()) != -1;
this.isAudio = ["mp3", "wav", "flac", "m4a", "aac", "pcm"]
.indexOf(this.file.getFileType()) != -1;
this.isImage = ["png", "jpg", "jpeg", "gif"]
.indexOf(this.file.getFileType()) != -1;
if (this.isVideo) {
this.mediaInitializer = this.initVideoPlayer;
} else if (this.isAudio) {
this.mediaInitializer = this.initAudioPlayer;
} else if (this.isImage) {
this.mediaInitializer = this.initImageViewer;
}
}
/**
* 视频播放器初始化
*
* @param fileUrl
*/
initVideoPlayer(fileUrl?: string): void {
console.log(this.mediaViewer.nativeElement); // => [object Object]
}
/**
* 音频播放器初始化
*
* @param fileUrl
*/
initAudioPlayer(fileUrl?: string): void {
...
}
/**
* 图片预览初始化
*
* @param fileUrl
*/
initImageViewer(fileUrl?: string): void {
...
}
}
|