Angular应用与Firebase数据:为什么我能看到上一页的数据?

5

所以,我有一个托管在Firebase上并使用Firestore进行数据存储的Angular 9应用程序。我有一个问题似乎非常简单,但是我无法理解为什么会出现这种情况。为了找到问题的根本原因,我大大简化了应用程序,并将尽力解释下面的问题。

这个应用程序:我有两个页面,一个首页和一个交易页面。这两个页面都从相同的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();
  }

结果: 这是每个步骤在控制台上输出的内容

  1. 我打开应用程序并进入主页 (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行内容?

1
你是否使用Firestore SDK启用了持久性? - Doug Stevenson
嗨@DougStevenson,你能详细解释一下吗?那是什么意思? - vdvaxel
https://firebase.google.com/docs/firestore/manage-data/enable-offline - Doug Stevenson
不,我没有启用它。 - vdvaxel
我认为是这样,我只是在尝试提出一些建议来帮助你,因为你的代码看起来还不错(再次说明,我不使用Firestore并直接从Angularfire使用Observables),因为我使用工具的方式不同,但也使用更基本的交互。试图帮助你指向一些可以尝试的东西,以防它显示出问题或给你下一步的想法。 - Steven Scott
显示剩余2条评论
3个回答

1

在交易页面上,您会看到结果出现了两次。很可能是因为valueChanges从Firestore的内存缓存中快速返回数据,然后再从真实数据中读取。在您的情况下,当4行数据在主页上返回并从缓存中处理时,它们也会被缓存并在交易页面上进行处理。


你知道如何告诉Firebase不要从缓存中读取吗? - vdvaxel
1
似乎是一个已知问题:https://github.com/angular/angularfire/issues/2012。并且同意,推荐的解决方法似乎是“使用`get`而不是`valueChanges`”。 - amakhrov
@vdvaxel你尝试将 valueChanges 改为 get 了吗? - Kudratillo
嘿,我还没有尝试过。从我的观察来看,如果你只想获取一次数据,那么使用 get,如果想要在新数据到达时自动更新,则使用 valueChanges?所以如果我想让数据自动更新,get 可能不是最好的解决方案。 - vdvaxel
@vdvaxel 如果你的代码可以处理多个更新,那么就不用担心这个“缓存”问题。用户不应该注意到它。否则,您可以尝试使用throttle+distinctUntilChanged操作符组合,在非常短的时间内(例如200毫秒)获取最新值。 - Kudratillo
显示剩余3条评论

0

我认为问题出在这里:

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

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

ngOnDestroy() {
  this.txSubscription.unsubscribe();
}

你已经成功取消订阅了。但是当 onAuthStateChanged 再次触发时,请注意会发生什么。你的第一个订阅会丢失,而且没有办法取消订阅。我认为可以使用 switchmap 操作符在 onAuthStateChanged 上来解决这个问题。像这样:

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(...)

嗨,Robin,非常感谢你的建议。看起来你的解决方案可以工作,至少在交易页面上我不再看到主页上的4行了。你会建议完全不使用onAuthStateChanged吗?我使用它的原因是当我切换页面时出现了权限错误。但我不确定这是否是最好的解决方案... - vdvaxel
我认为你仍然可以像在你的示例中那样使用onAuthStateChange,但是在加载主页数据之前,你应该检查是否有活动订阅并取消订阅。个人而言,我更喜欢switchmap解决方案,因为我是rxjs的粉丝。我认为你会收到权限错误,因为switchmap没有检查用户是否已登录,你在身份验证状态更改中也没有这样做。我认为注销并刷新令牌也会触发状态。 - Robin Dijkhof
@vdvaxel,我更新了我的示例。不确定教程有什么问题。 - Robin Dijkhof
嗨,Robin,感谢您的回复,但这是否意味着我必须在应用程序中的每个读取操作中使用onAuthStateChanged和switchMap?这感觉不太对,我有一种核心问题在其他地方的感觉。 - vdvaxel
你可以创建一个路由守卫。 - Robin Dijkhof
显示剩余2条评论

0

这个问题可能是由于以下原因导致的

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页面吗? - vdvaxel
你好,我刚刚尝试了你的整个解决方案,但仍然遇到了与我最初的帖子中所述相同的问题(即在导航到交易页面时,在控制台中看到来自主页的数据)。 - vdvaxel
@Saurabh47g,你有什么想法吗? - vdvaxel
在手动输入URL的情况下检查用户身份验证,您可以使用canActivate路由守卫。我将在我的答案中提供示例。我不确定为什么在导航期间获取数据。 - Saurabh Gangamwar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接