Ionic3中Profile组件切换Tab时出现profileData为null错误排查
profileData为空错误 这个问题我之前也碰到过,结合你的代码和报错信息,核心原因主要有两个,咱们一步步拆解解决:
原因分析
持久化Firebase监听器引发的组件销毁后回调异常
你在constructor里用ref.on('value', ...)添加了一个持久化的数据监听器,但Ionic的Tab组件切换时,非当前活跃Tab的组件实例会被销毁(从DOM中移除)。此时这个监听器并没有被移除,当Firebase数据发生变化时,回调函数会继续执行,但组件实例已经不存在,this变成了null,所以会抛出Cannot read property 'profileData' of null的错误。第一个Tab正常是因为组件始终处于活跃状态,实例没有被销毁。profileData未初始化+OnPush变更检测的影响
你的profileData初始值是undefined,再加上用了ChangeDetectionStrategy.OnPush策略。当组件在非第一个Tab初始化时,如果Firebase回调还没执行(或者auth状态未就绪导致没匹配到用户数据),profileData还是undefined,模板里的*ngFor会尝试遍历一个未定义的值,就算加了安全操作符,位置不对也起不了作用。
解决方案
1. 组件销毁时移除Firebase监听器
先保存监听器的引用,然后在组件的ngOnDestroy生命周期钩子中移除它,避免内存泄漏和回调执行时的错误:
修改profile.ts:
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy } from '@angular/core'; import * as firebase from 'firebase'; import { AngularFireDatabase, AngularFireList } from 'angularfire2/database'; @Component({ selector: 'profile', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: 'profile.html' }) export class ProfileComponent implements OnDestroy { // 实现OnDestroy接口 profileRef: AngularFireList<any>; profileData: any[] = []; // 初始化为空数组,避免模板遍历未定义值 private valueListener: firebase.Unsubscribe; // 保存监听器引用 constructor(private database: AngularFireDatabase, private cdRef:ChangeDetectorRef) { console.log("Constructor") const ref = firebase.database().ref('/users/'); // 保存监听器返回的取消订阅函数 this.valueListener = ref.on('value', (snapshot) => { this.profileData = []; // 清空数组,避免重复添加数据 snapshot.forEach((child) => { // 先检查currentUser是否存在,避免auth状态未就绪时的报错 if(firebase.auth().currentUser && child.val().UID == firebase.auth().currentUser.uid){ console.log("Usuario logueado") console.log(snapshot) this.profileData.push({ name: child.val().name, lastname: child.val().lastname, phone: child.val().phone, direction: child.val().direction, followQuantity: child.val().followQuantity, points: child.val().points, sex: child.val().sex, }); this.cdRef.detectChanges(); } }); }) } // 组件销毁时移除Firebase监听器 ngOnDestroy() { if(this.valueListener) { this.valueListener(); } } }
2. 优化模板遍历逻辑
在模板里给profileData加上兜底逻辑,避免未初始化时的报错:
修改profile.html:
<div> <ion-item no-lines class="user" *ngFor="let data of profileData || []"> <img class="user-img" src="../../assets/imgs/logo.png" /> <h4>Nombre: {{ data.name }}</h4> <h4>Siguiendo: {{ data.followQuantity }}</h4> <h4>Puntos: {{ data.points }}</h4> </ion-item> </div>
3. 更可靠的Auth状态监听(可选但推荐)
直接用firebase.auth().currentUser可能存在时机问题,比如组件初始化时auth状态还没加载完成,导致无法匹配用户数据。可以用AngularFireAuth监听auth状态,确保只有用户登录后才去获取数据:
import { AngularFireAuth } from 'angularfire2/auth'; import { Subscription } from 'rxjs/Subscription'; // ... export class ProfileComponent implements OnDestroy { // ... private authSubscription: Subscription; constructor( private database: AngularFireDatabase, private cdRef:ChangeDetectorRef, private afAuth: AngularFireAuth // 注入AngularFireAuth ) { // 监听auth状态变化 this.authSubscription = this.afAuth.authState.subscribe(user => { if(user) { // 用户已登录,再去获取用户数据 const ref = firebase.database().ref('/users/'); this.valueListener = ref.on('value', (snapshot) => { this.profileData = []; snapshot.forEach((child) => { if(child.val().UID == user.uid){ this.profileData.push({ // ... 你的用户数据字段 }); this.cdRef.detectChanges(); } }); }) } else { // 用户未登录,清空数据 this.profileData = []; this.cdRef.detectChanges(); } }); } ngOnDestroy() { if(this.valueListener) { this.valueListener(); } if(this.authSubscription) { this.authSubscription.unsubscribe(); } } }
这样修改后,不仅能解决Tab切换时的报错,还能更稳妥地处理用户登录状态的变化。
内容的提问来源于stack exchange,提问作者Juanma Perez

