Angular 5中使用Google Maps API页面导航后出现的问题
这个问题的核心原因很明确:你的活动详情页组件被销毁后,之前订阅的route.data回调依然在执行,导致initMap函数尝试初始化一个已经不存在的DOM元素。
为什么会触发这个错误?
当你从活动详情页导航到其他页面时,Angular会销毁EventBuyComponent组件,对应的DOM元素(包括#map容器)也会被移除。但如果你没有取消this.route.data的订阅,后续的路由操作(两次导航后可能触发了路由数据的更新或者订阅的回调被意外触发)会再次调用initMap函数。这时候document.getElementById('map')会返回null,而Google Maps的构造函数尝试访问这个null对象的firstChild属性,就会抛出你看到的错误。
为什么搜索页没有这个问题?
大概率是因为搜索页的组件是持久化的(比如它属于主路由的一部分,不会在导航时被销毁),或者搜索页的代码正确清理了路由数据的订阅,所以不会在组件不存在时执行地图初始化逻辑。
解决方法
1. 组件销毁时取消订阅(最关键)
Angular的订阅如果不手动取消,会在组件销毁后依然存在,导致内存泄漏和意外的回调执行。你需要把订阅保存到一个Subscription对象中,然后在ngOnDestroy钩子中退订:
import { Subscription } from 'rxjs'; import { OnDestroy } from '@angular/core'; // 确保组件实现OnDestroy接口 export class EventBuyComponent implements OnInit, OnDestroy { private routeDataSubscription: Subscription; private map: google.maps.Map; // 保存地图实例,方便销毁 ngOnInit() { this.routeDataSubscription = this.route.data.subscribe((routeData: { data: Event }) => { // ... 你的数据处理逻辑 this.initMap(); }); } ngOnDestroy() { // 取消路由数据订阅 if (this.routeDataSubscription) { this.routeDataSubscription.unsubscribe(); } // 清理Google Maps实例,避免内存泄漏 if (this.map) { google.maps.event.clearInstanceListeners(this.map); this.map = null; } } initMap() { if (this.event && this.event.location.coordinates) { const location = new google.maps.LatLng(this.event.location.coordinates[1], this.event.location.coordinates[0]); const mapCanvas = document.getElementById('map'); const mapOptions = { center: location, zoom: 16, panControl: false, mapTypeId: google.maps.MapTypeId.ROADMAP, clickableIcons: false, mapTypeControl: false }; this.map = new google.maps.Map(mapCanvas, mapOptions); // 把地图实例赋值给类属性 const marker = new google.maps.Marker({ icon: "/assets/img/marker-normal.png", position: location, map: this.map }); this.mapInitialized = true; } } }
2. 初始化前检查DOM元素是否存在
在initMap函数开头,先确认#map容器是否存在,避免传入null给Google Maps构造函数:
initMap() { if (!this.event?.location?.coordinates) return; const mapCanvas = document.getElementById('map'); // 先检查元素是否存在 if (!mapCanvas) { console.warn('Map container not found, skipping initialization'); return; } const location = new google.maps.LatLng(this.event.location.coordinates[1], this.event.location.coordinates[0]); const mapOptions = { center: location, zoom: 16, panControl: false, mapTypeId: google.maps.MapTypeId.ROADMAP, clickableIcons: false, mapTypeControl: false }; const map = new google.maps.Map(mapCanvas, mapOptions); const marker = new google.maps.Marker({ icon: "/assets/img/marker-normal.png", position: location, map: map }); this.mapInitialized = true; }
3. 使用Angular的ViewChild获取DOM元素(更规范)
用Angular的ViewChild代替原生的document.getElementById,可以更准确地获取组件内的DOM元素,并且确保在元素渲染完成后再执行初始化:
首先在模板中给地图容器添加模板引用变量:
<div #mapContainer id="map"></div>
然后在组件中获取并使用这个元素:
import { ViewChild, ElementRef, AfterViewInit } from '@angular/core'; export class EventBuyComponent implements OnInit, OnDestroy, AfterViewInit { @ViewChild('mapContainer') mapContainer: ElementRef; private map: google.maps.Map; ngAfterViewInit() { // 这里可以确保DOM元素已经渲染完成 } initMap() { if (!this.event?.location?.coordinates || !this.mapContainer?.nativeElement) return; const location = new google.maps.LatLng(this.event.location.coordinates[1], this.event.location.coordinates[0]); const mapOptions = { center: location, zoom: 16, panControl: false, mapTypeId: google.maps.MapTypeId.ROADMAP, clickableIcons: false, mapTypeControl: false }; this.map = new google.maps.Map(this.mapContainer.nativeElement, mapOptions); const marker = new google.maps.Marker({ icon: "/assets/img/marker-normal.png", position: location, map: this.map }); this.mapInitialized = true; } }
总结
最核心的修复是在组件销毁时取消路由数据的订阅,避免组件不存在时执行DOM相关操作。结合检查DOM元素存在性或者使用ViewChild,可以彻底解决这个问题。
内容的提问来源于stack exchange,提问作者Victoria Ruiz

