Angular页面重载后新增玩家致旧玩家消失问题求助
Angular + Firebase 游戏页面重载后添加新玩家丢失旧数据问题
问题详情
我用Angular结合Firebase开发Ring of Fire克隆游戏,此前功能运行正常。新增显示玩家姓名与头像的功能后,出现页面重载后添加新玩家时旧玩家数据消失的问题,确定该问题与游戏数据的初始加载逻辑有关,但作为Angular新手无法修复。
DialogAddPlayerComponent 代码
import { Component, OnInit, inject } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; import { FormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { NgFor } from '@angular/common'; import { MatSelectModule } from '@angular/material/select'; import { MatFormFieldModule } from '@angular/material/form-field'; import { GameComponent } from '../game/game.component'; import { AddService } from '../add-service/add.service'; import { ActivatedRoute } from '@angular/router'; interface Avatar { value: number; viewValue: any; name: string; } @Component({ selector: 'app-dialog-add-player', templateUrl: './dialog-add-player.component.html', styleUrls: ['./dialog-add-player.component.scss'], }) export class DialogAddPlayerComponent implements OnInit { name: string = ''; avatars: Avatar[] = []; selectedAvatar: any; currentId!: any; constructor( public dialogRef: MatDialogRef<DialogAddPlayerComponent>, private route: ActivatedRoute, public gameComponent: GameComponent ) {} async ngOnInit(): Promise<void> { await this.getCurrentId(); this.getAvatarImages(); } onNoClick() { this.dialogRef.close(); } async sendPlayerData() { let newPlayer = { name: this.name, avatars: this.selectedAvatar }; let stringData = JSON.stringify(newPlayer); await this.gameComponent.addPlayer(stringData, this.currentId); } async getCurrentId() { this.currentId = localStorage.getItem('id'); } getAvatarImages() { for (let i = 1; i < 51; i++) { this.avatars.push({ value: i, viewValue: '/assets/img/avatars_withbg/' + i + '.png', name: this.names[i - 1], }); } } names = [ 'Elowen', 'Aldric', 'Thorn', 'Maelis', 'Caelan', 'Brynn', 'Eowyn', 'Lysander', 'Rowena', 'Crispin', 'Seraphina', 'Oswin', 'Lorelei', 'Cassian', 'Elysia', 'Aurelius', 'Ilythia', 'Orin', 'Morgana', 'Finnian', 'Genevieve', 'Peregrine', 'Elara', 'Quinlan', 'Seraphim', 'Rhiannon', 'Cedric', 'Ondine', 'Eadric', 'Isolde', 'Thaddeus', 'Aurora', 'Gawain', 'Artemis', 'Calista', 'Gareth', 'Thalassa', 'Cyprian', 'Eulalia', 'Drystan', 'Morgause', 'Ezrael', 'Eirian', 'Faelan', 'Sylvaine', 'Lorcan', 'Morrigan', 'Fiora', 'Finlo', 'Danilo', ]; }
GameComponent 代码
import { Component, Injectable, OnInit, inject } from '@angular/core'; import { Firestore, collectionData, collection, doc, addDoc, updateDoc, } from '@angular/fire/firestore'; import { Game } from 'src/models/game'; import { MatDialog, MAT_DIALOG_DATA, MatDialogRef, MatDialogModule, } from '@angular/material/dialog'; import { NgIf } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { FormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { DialogAddPlayerComponent } from '../dialog-add-player/dialog-add-player.component'; import { BehaviorSubject, Observable } from 'rxjs'; import { onSnapshot } from '@firebase/firestore'; import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-game', templateUrl: './game.component.html', styleUrls: ['./game.component.scss'], }) @Injectable() export class GameComponent implements OnInit { game: Game = new Game(); unsubGamesList; unsubSingleDoc; currentId: string = ''; firestore: Firestore = inject(Firestore); constructor(private route: ActivatedRoute, public dialog: MatDialog) { this.unsubGamesList = onSnapshot(this.getGamesRef(), (list: any) => { list.forEach((element: any) => { //console.log(element.data()); }); }); this.unsubSingleDoc = onSnapshot( this.getSingleDocRef('games', '1234'), (doc) => {} ); } ngOnInit(): void { //route gibt url-rest zurück --> dieser ist gleichzeitig die firebase-id und kann // so über einen snapshot-listener das zugehörige spiel anzeigen und updaten this.route.params.subscribe((params) => { this.unsubSingleDoc = onSnapshot( this.getSingleDocRef('games', params['id']), (gameDoc) => { const gameData = gameDoc.data(); this.currentId = params['id']; localStorage.setItem('id', this.currentId); if (gameData) { this.game.currentPlayer = gameData['currentPlayer']; this.game.playedCards = gameData['playedCards']; this.game.players = this.getNames(gameData['players']); this.game.stack = gameData['stack']; this.game.pickCardAnimation = gameData['pickCardAnimation']; this.game.currentCard = gameData['currentCard']; this.game.avatarId = this.getAvatars(gameData['players']);; } } ); }); } getNames(playersList: any) { const namesArray = playersList.map((jsonString: string) => { const jsonObject = JSON.parse(jsonString); return jsonObject.name; }); return namesArray; } getAvatars(playersList: any){ const avatarArray = playersList.map((jsonString: string) => { const jsonObject = JSON.parse(jsonString); return jsonObject.avatars; }); return avatarArray; } ngOnDestroy(): void { this.unsubGamesList(); this.unsubSingleDoc(); } getGamesRef() { return collection(this.firestore, 'games'); } getSingleDocRef(colId: string, docId: string) { return doc(collection(this.firestore, colId), docId); } async addGame(item: {}) { await addDoc(this.getGamesRef(), item) .catch((err) => { console.log('Adding document failed: ' + err); }) .then((docRef: any) => { this.currentId = docRef.id; console.log('Document written with id: ' + docRef.id); }); } async updateGame() { if (this.currentId) { try { let gameDocRef = this.getSingleDocRef('games', this.currentId); await updateDoc(gameDocRef, this.game.toJson()); console.log('Game successfully updated'); } catch (error) { console.error('Error while update:', error); } } else if (!this.currentId) { console.error('currentId is not available'); } } takeCard() { if (!this.game.pickCardAnimation) { this.game.currentCard = this.game.stack.pop()!; this.game.pickCardAnimation = true; //console.log(this.game.playedCards); this.game.currentPlayer++; this.game.currentPlayer = this.game.currentPlayer % this.game.players.length; this.updateGame(); setTimeout(() => { this.game.playedCards.push(this.game.currentCard); this.game.pickCardAnimation = false; this.updateGame(); }, 1000); } } async addPlayer(playerData: string, id: string) { this.currentId = id; console.log('addPlayer executed, playerData:', playerData); console.log('currentID:', this.currentId); this.game.players.push(playerData); await this.updateGame(); } openDialog(): void { const dialogRef = this.dialog.open(DialogAddPlayerComponent); dialogRef.afterClosed().subscribe(() => { //name in subscription!? /* if (name && name.length > 0) { this.game.players.push(name); this.updateGame(); } */ }); } }
Game 模型代码
export class Player { public id: number; public name: string; constructor(id: number, name: string) { this.id = id; this.name = name; } } export class Game{ //public players: Player[] = []; public players: string[] = []; public stack: string[] = []; public playedCards: string[] = []; public currentPlayer: number = 0; public pickCardAnimation = false; public currentCard: string = ''; public avatarId: string = ''; constructor(){ for (let i = 1; i < 14; i++) { this.stack.push('hearts_'+i); this.stack.push('ace_'+i); this.stack.push('clubs_'+i); this.stack.push('diamonds_'+i); } shuffle(this.stack); } public toJson(){ return { players: this.players, stack: this.stack, playedCards: this.playedCards, currentPlayer: this.currentPlayer, pickCardAnimation: this.pickCardAnimation, currentCard: this.currentCard, avatarId: this.avatarId, } } } function shuffle(array:string[]) { let currentIndex = array.length, randomIndex; // While there remain elements to shuffle. while (currentIndex > 0) { // Pick a remaining element. randomIndex = Math.floor(Math.random() * currentIndex); currentIndex--; // And swap it with the current element. [array[currentIndex], array[randomIndex]] = [ array[randomIndex], array[currentIndex]]; } return array; }
问题根源与修复方案
核心问题
在GameComponent的ngOnInit中,加载Firebase数据时,你把game.players替换成了解析后的纯姓名数组,而非保留Firebase存储的原始JSON字符串数组:
// 错误代码 this.game.players = this.getNames(gameData['players']);
这会导致:
- 页面重载后,
game.players变为纯姓名字符串数组,破坏了原本的JSON字符串数组结构 - 新增玩家时,往数组中push玩家JSON字符串,导致数组同时存在两种格式数据
- 再次更新Firebase时数据结构混乱,重载后解析失败,旧玩家数据丢失
同时,game.avatarId被赋值为头像数组,但模型中它是单个字符串类型,存在数据类型不匹配的隐患。
修复步骤
修正GameComponent的数据加载逻辑
在组件中新增两个属性用于存储解析后的姓名和头像(仅用于页面展示),保留game.players为原始JSON字符串数组(用于Firebase更新):// 在GameComponent类中新增属性 playerNames: string[] = []; playerAvatars: any[] = []; // 修改ngOnInit中的数据加载部分 ngOnInit(): void { this.route.params.subscribe((params) => { this.unsubSingleDoc = onSnapshot( this.getSingleDocRef('games', params['id']), (gameDoc) => { const gameData = gameDoc.data(); this.currentId = params['id']; localStorage.setItem('id', this.currentId); if (gameData) { this.game.currentPlayer = gameData['currentPlayer']; this.game.playedCards = gameData['playedCards']; // 保留原始JSON字符串数组,用于后续Firebase更新 this.game.players = gameData['players']; // 解析出姓名和头像,仅用于页面展示 this.playerNames = this.getNames(gameData['players']); this.playerAvatars = this.getAvatars(gameData['players']); this.game.stack = gameData['stack']; this.game.pickCardAnimation = gameData['pickCardAnimation']; this.game.currentCard = gameData['currentCard']; // 删除错误的avatarId数组赋值(模型中为单个字符串) // this.game.avatarId = this.getAvatars(gameData['players']); } } ); }); }修正Game模型的avatarId属性
如果不需要单个头像ID,直接删除该属性;如果需要(比如存储当前玩家头像),明确其用途为单个值而非数组:export class Game{ public players: string[] = []; public stack: string[] = []; public playedCards: string[] = []; public currentPlayer: number = 0; public pickCardAnimation = false; public currentCard: string = ''; // 删除无用的avatarId,或改为当前玩家头像ID // public avatarId: string = ''; }修改页面模板绑定
页面中显示玩家姓名和头像的部分,改为绑定新增的playerNames和playerAvatars数组:<!-- 示例:循环展示玩家 --> <div *ngFor="let name of playerNames; let i = index"> <img [src]="playerAvatars[i]" alt="{{name}}"> <span>{{name}}</span> </div>验证addPlayer逻辑
addPlayer方法中往game.playerspush玩家JSON字符串的逻辑是正确的,无需修改,确保新增玩家时数据结构一致。
内容的提问来源于stack exchange,提问作者HolyPoly
相关产品推荐
相关产品推荐

