带有 Firebase 数据的 Angular 应用程序:为啥我会看到上一页的数据?

Posted

技术标签:

【中文标题】带有 Firebase 数据的 Angular 应用程序:为啥我会看到上一页的数据?【英文标题】:Angular app with Firebase data: why am I seeing data from the previous page?带有 Firebase 数据的 Angular 应用程序:为什么我会看到上一页的数据? 【发布时间】:2020-12-01 09:54:38 【问题描述】:

所以,我在 Firebase 上托管了一个 Angular 9 应用程序,并使用 Firestore 存储数据。我有一个看起来很简单的问题,但我无法理解它为什么会发生。我已经对应用程序进行了很多简化以找到问题的根本原因,并将尝试在下面尽可能解释该问题。

应用程序:我有 2 个页面、一个主页和一个交易页面。两个页面都从同一个 Firebase 集合“事务”中读取。但是,在主页上,我想显示最近的 4 笔交易(按日期排序,降序),而在交易页面上,我想显示 10 笔最赚钱的交易(按金额排序,降序)。目前,我只是将数据记录到控制台进行调试。在记录数据之前,我还稍微对其进行了操作(参见下面的代码)。

问题:当我在首页开始时,我可以在控制台中按预期看到我最近的 4 笔交易。但是,当我转到“事务”页面时,它会在短时间内再次在控制台中记录 4 个最近的事务,这些事务应该只显示在主页上。大约一秒钟后,它显示了预期的 10 笔最有利可图的交易。

代码: 这是我的 home.page.ts 代码:

  txSubscription: Subscription;

  constructor(
    public afAuth: AngularFireAuth,
    private readonly firestore: AngularFirestore
  )  

  // Function to get the 4 most recent transactions
  async getRecentTransactions() 
    this.txSubscription = this.firestore
      .collection('transactions', ref => ref.orderBy('date', 'desc').limit(4))
      .valueChanges()
      .subscribe(rows => 
        this.recentTransactions = [];

        rows.forEach(row => 
          let jsonData = ;
          jsonData['ticker'] = (row['ticker'].length <= 10 ? row['ticker'] : row['ticker'].substring(0, 10) + '...');
          jsonData['date'] = formatDate(row['date'].toDate(), 'dd/MM/y', 'en');
    
          jsonData['amount'] = prefix + formatNumber(row['netAmount'], 'be', '1.2-2');
    
          this.recentTransactions.push(jsonData);
        )

        console.log("home page", this.recentTransactions);
      )
  

  ngOnInit() 
    this.afAuth.onAuthStateChanged(() => 
      this.getRecentTransactions();
    )
  

  ngOnDestroy() 
    this.txSubscription.unsubscribe();
  

和transaction.page.ts的代码非常相似:

  txSubscription: Subscription;

  constructor(
    public afAuth: AngularFireAuth,
    private readonly firestore: AngularFirestore
  )  

  // Function to load the data for the home page
  loadHomeData() 
    this.txSubscription = this.firestore
      .collection('transactions', ref => ref.orderBy('profitEur', 'desc').limit(10))
      .valueChanges()
      .subscribe(rows => 
        this.resultRows = [];

        rows.forEach(row => 
          this.resultRows.push(row['ticker'].slice(0, 8));
        );

        console.log("transaction page", this.resultRows);
      )
  

  ngOnInit() 
    this.afAuth.onAuthStateChanged(() => 
      this.loadHomeData();
    )
  

  ngOnDestroy() 
    this.txSubscription.unsubscribe();
  

结果:这是每一步输出到控制台的内容

    我在主页上打开应用程序(如预期的 4 行):
home page (4) […, …, …, …]
  0: ticker: "BAR", date: "21/07/2020", amount: "- € 1 086,10"
  1: ticker: "ASL C340.0...", date: "18/07/2020", amount: "€ 0,00"
  2: ticker: "ASL C340.0...", date: "14/07/2020", amount: "- € 750,85"
  3: ticker: "TUI C7.00 ...", date: "20/06/2020", amount: "€ 0,00"
  length: 4
  __proto__: Array(0)
    我导航到交易页面:
transaction page (4) ["TUI C7.0", "ASL C340", "BAR", "ASL C340"]

transaction page (10) ["ASL C240", "ASL C232", "REC", "ASL C270", "ASL C310", "ASML", "ASL P220", "BAR", "CFEB", "MELE"]

为什么导航到主页时,控制台中的主页再次显示相同的 4 行?

【问题讨论】:

您是否使用 Firestore SDK 启用了持久性? 嗨@DougStevenson,你能详细说明一下吗?这到底是什么意思? firebase.google.com/docs/firestore/manage-data/enable-offline 不,我没有启用它。 它应该自动发生,但可能会清除 OnDestroy(); 中的数据数组;我假设来自 Firestore 的数据是通过服务获得的,并且它们很可能是主模块中的单例,因此它们在整个应用程序中保持活跃。 【参考方案1】:

您在交易页面上获得了 2 次结果。 valueChanges 很可能会从 firestore 内存缓存中返回数据作为快速响应,然后从真实数据中读取。在您的情况下,当它们在主页上返回时会缓存 4 行,并从事务页面上的缓存中处理它们。

【讨论】:

你知道如何告诉 Firebase 不要从缓存中读取吗? @vdvaxel 我建议你检查这个答案***.com/questions/52700648/… 从我在那个问题中可以看到,Firestore 在去缓存之前总是会先从服务器读取数据? 是的,意思是尝试使用get而不是valueChanges 似乎是一个已知问题:github.com/angular/angularfire/issues/2012。同意,推荐的解决方法似乎是“使用get 而不是valueChanges【参考方案2】:

我认为问题出在这里:

loadHomeData() 
  this.txSubscription = this.firestore
  ...
  .subscribe(rows => 
    ...
  )


ngOnInit() 
  this.afAuth.onAuthStateChanged(() => 
    this.loadHomeData();
  )


ngOnDestroy() 
  this.txSubscription.unsubscribe();

您的退订是正确的。但是,看看当 onAuthStateChanged 再次触发时会发生什么。您的第一个订阅已丢失,无法取消订阅。我认为在onAuthStateChanged 上使用switchmap 运算符可以解决您的问题。像这样的:

this.subscription = this.af.authState.pipe(
  switchMap(auth => 
    if(auth !== null && auth !== undefined)
      return   this.firestore.collection('transactions', ref => ref.orderBy('profitEur', 'desc').limit(10)).valueChanges())
     else
      throw "Not loggedin";
    
).subscribe(...)

【讨论】:

嗨罗宾,非常感谢您的建议。它似乎适用于您的解决方案,至少在交易页面上,我不再看到主页上的 4 行。你会建议根本不使用 onAuthStateChanged 吗?我使用的原因是因为我切换页面时出现权限错误。我不确定这是否是最好的解决方案...... 嗨罗宾,我已经按照本教程 (dev.to/dobis32/…) w.r.t.身份验证和身份验证时加载数据。但是,我仍然遇到同样的问题。您知道本教程会出现什么问题吗? 我认为您仍然可以像在示例中那样使用 onAuthStateChange,但您应该在加载家庭数据之前检查您是否有活动订阅并取消订阅。我个人喜欢 switchmap 解决方案,因为我是 rxjs 的粉丝。我认为您会收到权限错误,因为 switchmap 不会检查用户是否已登录,并且您的身份验证状态也没有改变。我虽然注销并刷新令牌也会触发状态。 @vdvaxel 我更新了我的示例。不知道教程有什么问题。 嗨罗宾,感谢您的回复,但这是否意味着我必须在我的应用程序中使用 onAuthStateChanged 和 switchMap 进行每次读取操作?只是感觉不对,我觉得核心问题出在其他地方。【参考方案3】:

这个问题的发生可能是因为

this.afAuth.onAuthStateChanged()

被触发两次。

而不是检查每个组件的身份验证状态。 您可以简单地在app.component.ts订阅身份验证状态。如果用户未经身份验证或身份验证状态更改,它将重定向到登录页面,否则将重定向到 home.page.ts

export class AppComponent 
  constructor(private readonly auth: AngularFireAuth, private router: Router) 
    this.auth.authState.subscribe(response => 
      console.log(response);
      if (response && response.uid) 
        this.router.navigate(['dashboard', 'home']); //Home page route
       else 
        this.router.navigate(['auth', 'login']); //Login page route
      
    , error => 
      this.auth.signOut();
      this.router.navigate(['auth', 'login']); //Login Page route
    );
  

在您的 home.component.ts 和 transaction.page.ts 中无需检查身份验证状态。

home.component.ts
 txSubscription: Subscription;

  constructor(
    public afAuth: AngularFireAuth,
    private readonly firestore: AngularFirestore
  )  

  // Function to get the 4 most recent transactions
  async getRecentTransactions() 
    this.txSubscription = this.firestore
      .collection('transactions', ref => ref.orderBy('date', 'desc').limit(4))
      .valueChanges()
      .subscribe(rows => 
        this.recentTransactions = [];

        rows.forEach(row => 
          let jsonData = ;
          jsonData['ticker'] = (row['ticker'].length <= 10 ? row['ticker'] : row['ticker'].substring(0, 10) + '...');
          jsonData['date'] = formatDate(row['date'].toDate(), 'dd/MM/y', 'en');
    
          jsonData['amount'] = prefix + formatNumber(row['netAmount'], 'be', '1.2-2');
    
          this.recentTransactions.push(jsonData);
        )

        console.log("home page", this.recentTransactions);
      )
  

  ngOnInit() 
      this.getRecentTransactions();
  

  ngOnDestroy() 
    this.txSubscription.unsubscribe();
  
transaction.page.ts
 txSubscription: Subscription;

  constructor(
    public afAuth: AngularFireAuth,
    private readonly firestore: AngularFirestore
  )  

  // Function to load the data for the home page
  loadHomeData() 
    this.txSubscription = this.firestore
      .collection('transactions', ref => ref.orderBy('profitEur', 'desc').limit(10))
      .valueChanges()
      .subscribe(rows => 
        this.resultRows = [];

        rows.forEach(row => 
          this.resultRows.push(row['ticker'].slice(0, 8));
        );

        console.log("transaction page", this.resultRows);
      )
  

  ngOnInit() 
      this.loadHomeData();
  

  ngOnDestroy() 
    this.txSubscription.unsubscribe();
  

【讨论】:

您好,我目前正在尝试您的建议解决方案,感谢您的详细回复。我确实有一个问题:当我启动应用程序并到达登录屏幕时,但我手动将 URL 从 /login 更改为 /home,我仍然可以看到主页。它不应该根据您为 app.component.ts 建议的代码将我重定向到 /login 页面吗? 再次您好,我刚刚尝试了您的整个解决方案,但我仍然遇到与我最初帖子中解释的相同的问题(即在导航到交易页面时在控制台中查看主页中的数据)。 对此@Saurabh47g 有任何想法吗? 用于在手动输入 url 的情况下检查用户身份验证。您可以使用 canActivate 路由保护。我将用示例更新我的答案。我不确定为什么在导航期间获取数据。

以上是关于带有 Firebase 数据的 Angular 应用程序:为啥我会看到上一页的数据?的主要内容,如果未能解决你的问题,请参考以下文章

带有 Firebase 的 Angular - 应用检查后权限缺失或不足

使用 Firebase 云功能为带有 i18n 的 Angular 10 s-s-r 通用应用程序提供服务

在 Ionic2/Angular2 应用程序中使用 ngFor 获取 Firebase 数据库结果

Angular 4 firebase 下拉选择标签选项

在 ngFor 中使用带有 angularfire2 的 firebase-src 指令失败

Angular 应用程序中的 Firebase UI Auth 小部件