You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular中数据库变更后ngOnInit多次调用的问题排查

Angular Firestore 删除记录后数据重复加载问题

我开发的Angular应用中,用户点击按钮会从Firestore数据库删除记录。控制台显示ngOnInit先以旧数据调用1次,再以更新后的新数据调用2次,导致数组中存入3条数据而非1条。尝试用take(1)操作符仅获取首次响应,但得到的是变更前的旧数据;尝试启用OnPush变更检测却出现空白页面。


相关代码

swap-requests.html

<ion-content>
  <ion-grid class="ion-grid-width-md" *ngIf="user" >
    <tr *ngFor="let swaps of userSwapRequests;">
      <ion-row class="ion-padding" size="auto">
        <ion-col>
          <td *ngFor="let user of swaps.userCard">
            <ion-img [src]="user.images.small"
                     style="background: #F2F2F2;"></ion-img>
          </td>
        </ion-col>
        <ion-col>
          <td *ngFor="let friend of swaps.friendCard">
            <ion-img [src]="friend.images.small"
                     style="background: #F2F2F2;"></ion-img>
            </td>
        </ion-col>
      </ion-row>
      <ion-row>
        <ion-col>
          <ion-button expand="block" color="danger" (click)="cancelSwap(swaps.swapData, swaps)">Cancel Swap</ion-button>
        </ion-col>
      </ion-row>
    </tr>
  </ion-grid>
</ion-content>

swap-requests.page.ts

@Component({
  selector: 'app-swap-requests',
  templateUrl: './swap-requests.page.html',
  styleUrls: ['./swap-requests.page.scss'],
})

export class SwapRequestsPage implements OnInit {
  user: any[] = [];
  userSwapRequests: any[] = [];
  currentFriend: User[] = [];
  swapArray: any[] = [];

  constructor(
    private dataService: DataService,
    private pokeService: PokemoncardsService,
    private ToastCtrl: ToastController,
  ) {
    
  }

  ngOnInit() {
    this.dataService.loadUser(environment.currentUser).pipe(take(1)).subscribe((res: User[]) => {
      this.user.push(res);
      console.log('user', this.user);

      this.getUserPendingRequests();
    });
  }

  // 获取好友发起的请求
  getUserPendingRequests() {
    this.userSwapRequests = [];

    let userSwapsList = this.user.map(data => data.swap_request);

    this.buildSwapArray(this.userSwapRequests, userSwapsList);
  }


  getFriend(value: any) {
    let friend: any[] = [];

    this.dataService.loadUser(value).subscribe((res: User[]) => {
      friend.push(res);
    });
    return friend;
  }

  getPokemonCard(number: any) {
    let result: any = [];
    this.pokeService.getPokeCard(number).subscribe(res => {
      result.push(res);
    });
    return result;
  }

  // 整合API响应和好友数据,生成新数组
  async buildSwapArray(swaplist: any, userList: any) {

    userList.forEach((array: any) => {
      array.forEach((swap: any) => {
        let card: any;
        let usercard: any;
        let friend: any;

        Object.entries(swap).find(([key, value]) => {
          if (key === 'friend_card') {
            card = this.getPokemonCard(value);
            
          }
          if (key === 'user_card') {
            usercard = this.getPokemonCard(value);;
          }
          if (key === 'friend_id') {
            friend = this.getFriend(value);
          }
        })
        let newArray = {
          'friendCard': card,
          'userCard': usercard,
          'friend': friend,
          'swapData': swap,
        }
        swaplist.push(newArray);
        console.log('swapArray', this.userSwapRequests);
      })
    })
  }

  async cancelSwap(swapArray: any) {
    console.log('swapArray', swapArray);
    this.dataService.deleteSwapRequest(swapArray);

    let id = this.user.map(data => data.id);
    let friend: any;
    id.forEach((array) => {
      friend = array;
    })

    let friendArray: any = {
      'friend_card': swapArray.user_card,
      'friend_id': friend,
      'user_card': swapArray.friend_card,
    }
    let friendId = swapArray.friend_id;
    this.dataService.deleteFriendSwapRequest(friendArray, friendId);

    console.log('testsplice', this.userSwapRequests);
    const toast = await this.ToastCtrl.create({
      message: 'Swap confirmed',
      duration: 2000
    });
    toast.present();
  }

}

DataService

export class User {
  id?: string;
  cards: [] = [];
  user_name: string | undefined;
  friends: [] = [];
  wishlist: [] = [];
  swaps: [] = [];
  pending_swap: [] = [];
  avatar: string | undefined;
  swap_request: [] = [];
  confirmed_swap: [] = [];
}

@Injectable({
  providedIn: 'root'
})
export class DataService {
  constructor(private firestore: Firestore) { }

  loadUser(id: any): Observable<User[]> {
    const result = doc(this.firestore, `users/${id}`);
    return docData(result, { idField: 'id' }) as Observable<User[]>;
  }

  deleteSwapRequest(swapRequest: any) {
    const cardsIdRef = doc(this.firestore, `users/${environment.currentUser}`);
    return updateDoc(cardsIdRef, {
      swap_request: arrayRemove(swapRequest)
    });
  }

  deleteFriendSwapRequest(swapRequest: any, friend:any) {
    const cardsIdRef = doc(this.firestore, `users/${friend}`);
    return updateDoc(cardsIdRef, {
      pending_swap: arrayRemove(swapRequest)
    });
  }
}

问题分析与修复方案

核心问题

  1. Firestore实时监听数据堆积:docData默认是实时监听文档变化的Observable,每次文档更新(包括删除操作后)都会推送新数据。原代码用push添加数据,导致旧数据残留;用take(1)则会丢失后续更新。
  2. 异步数据处理逻辑错误:getFriend和getPokemonCard同步返回空数组,异步订阅的结果无法正确关联到视图,导致数据加载异常。
  3. OnPush变更检测失效:启用OnPush后,组件依赖的数据未通过可观察对象或输入属性触发变更检测,导致视图空白。

具体修复步骤

1. 正确处理Firestore实时数据

去掉take(1),保持实时监听,同时直接覆盖user数组避免数据堆积:

ngOnInit() {
  this.dataService.loadUser(environment.currentUser).subscribe((res: User[]) => {
    // 直接覆盖数组,替换旧数据
    this.user = [res];
    console.log('user', this.user);
    // 每次数据更新时重新构建请求列表
    this.getUserPendingRequests();
  });
}

2. 修复异步数据获取逻辑

将getFriend和getPokemonCard改为返回Observable,用forkJoin并行处理异步请求:

// 修改两个方法返回Observable
getFriend(value: any): Observable<User[]> {
  return this.dataService.loadUser(value);
}

getPokemonCard(number: any): Observable<any> {
  return this.pokeService.getPokeCard(number);
}

// 重写buildSwapArray确保数据就绪后再构建数组
async buildSwapArray(swaplist: any, userList: any) {
  swaplist.length = 0;

  for (const array of userList) {
    for (const swap of array) {
      // 并行获取所有异步数据
      const [friendCard, userCard, friend] = await forkJoin([
        this.getPokemonCard(swap.friend_card),
        this.getPokemonCard(swap.user_card),
        this.getFriend(swap.friend_id)
      ]).toPromise();

      swaplist.push({
        friendCard: [friendCard],
        userCard: [userCard],
        friend: [friend],
        swapData: swap,
      });
    }
  }
}

3. 删除后手动同步本地数据(提升体验)

在删除操作完成后,手动从userSwapRequests移除对应项:

async cancelSwap(swapData: any, swapItem: any) {
  // 等待两个删除操作完成
  await Promise.all([
    this.dataService.deleteSwapRequest(swapData),
    this.dataService.deleteFriendSwapRequest({
      friend_card: swapData.user_card,
      friend_id: this.user[0].id,
      user_card: swapData.friend_card,
    }, swapData.friend_id)
  ]);

  // 移除本地列表中的项
  const index = this.userSwapRequests.indexOf(swapItem);
  if (index > -1) {
    this.userSwapRequests.splice(index, 1);
  }

  const toast = await this.ToastCtrl.create({
    message: 'Swap cancelled',
    duration: 2000
  });
  toast.present();
}

4. 解决OnPush变更检测问题(可选)

若要启用OnPush,需用BehaviorSubject管理数据并配合async管道:

import { ChangeDetectionStrategy, BehaviorSubject } from '@angular/core';

@Component({
  selector: 'app-swap-requests',
  templateUrl: './swap-requests.page.html',
  styleUrls: ['./swap-requests.page.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SwapRequestsPage implements OnInit {
  private userSubject = new BehaviorSubject<User[]>([]);
  user$ = this.userSubject.asObservable();

  private swapRequestsSubject = new BehaviorSubject<any[]>([]);
  userSwapRequests$ = this.swapRequestsSubject.asObservable();

  ngOnInit() {
    this.dataService.loadUser(environment.currentUser).subscribe((res: User[]) => {
      this.userSubject.next([res]);
      this.getUserPendingRequests(res);
    });
  }

  async getUserPendingRequests(user: User) {
    const swaplist: any[] = [];
    await this.buildSwapArray(swaplist, [user.swap_request]);
    this.swapRequestsSubject.next(swaplist);
  }
}

模板中使用async管道:

<ion-grid class="ion-grid-width-md" *ngIf="user$ | async as user" >
  <tr *ngFor="let swaps of userSwapRequests$ | async;">
    <!-- 其余内容不变 -->
  </tr>
</ion-grid>

内容的提问来源于stack exchange,提问作者AimlesslyTrying

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 05:50:41