【问题标题】:AngularFireList - Avoid reloading all data on subscription change - Angular5/Ionic3AngularFireList - 避免重新加载订阅更改的所有数据 - Angular5/Ionic3
【发布时间】:2018-07-06 09:41:27
【问题描述】:

早上好,

我在从 firebase 加载列表并使用 AngularFire2 将其渲染到我的视图时遇到问题。

解释:

我的页面中有一个功能,允许加载帖子列表,并将帖子映射到用户数据,然后将整个列表绑定到视图:

时间线.ts:

// Get the timeline data
getTimeline() {
    console.log('Get timeline data');
    this.loadingProvider.show();
    this.createUserData(); // create user data if accoutn just created
    this.dataProvider.getCurrentUser().valueChanges().subscribe((user) => {
      this.user = <any>user;
    });

    this.getUserFriends().then((friends) => {
      this.friends = friends;
    }).then(() => {
      this.dataProvider.getTimelinePost().valueChanges().subscribe((posts) => {
        console.log("Change in timeline");
        this.timelineData = posts.slice(0).reverse().map(p => {
          return this.checkPostData(p);
        });
        this.loadingProvider.hide();
      });

    });
  }

// Get the friend's user so we can display only the post who are relevants for the user
  getUserFriends() {
    return new Promise((resolve) => {
      this.dataProvider.getFriends().valueChanges().subscribe((f) => {
        resolve(f)
      });
    });
  }

// Map post data with user's info such as avatar, etc
  checkPostData(currentPost) {
    let postedByFriend = this.checkIfPostedByFriend(currentPost);
    if (postedByFriend || currentPost.postBy === firebase.auth().currentUser.uid) {
      this.dataProvider.getUser(currentPost.postBy).valueChanges().subscribe((user) => {
        currentPost.avatar = user.img;
        currentPost.name = user.name
      });
      //  Liked ?
      this.dataProvider.getLike(currentPost.key).valueChanges().subscribe((likes) => {
        currentPost.likes = likes.length;
        currentPost.isLike = this.checkIfPostIsLiked(likes);
      });
      //  Disliked ?
      this.dataProvider.getdisLike(currentPost.key).valueChanges().subscribe((dislikes) => {
        currentPost.dislikes = dislikes.length;
        currentPost.isdisLike = this.checkIfPostIsDislike(dislikes);
      });
      // Commented ?
      this.dataProvider.getComments(currentPost.key).valueChanges().subscribe((comments) => {
        currentPost.comments = comments.length;
        currentPost.isComment = this.checkIfPostIsCommented(comments);
      });
    }
    return currentPost;
}

时间线.html:

    <ion-card *ngFor="let item of timelineData">
    <ion-item >
      <ion-avatar item-left>
        <img src="{{item.avatar}}"  >
      </ion-avatar>
      <h2>{{item.name}}</h2>
      <p>{{item.dateCreated | DateFormat}}</p>
    </ion-item>

    <ion-card-content>
      <p>{{item.postText}}</p>
    </ion-card-content>
    [...]
  </ion-card>

DataProvider.ts:

// Get Timeline post
  getTimelinePost(){
    return this.angularDb.list<any>('/timeline');
  }

我的问题如下:当我将帖子添加到时间线时,需要几秒钟,因为 Timeline.ts 页面会重新加载整个列表(这是正常的,因为有使用 getTimeLinePost() 进行的订阅)。我想知道是否可以仅检测列表中的最后更改以将它们添加到视图中,从而避免重新加载所有数据。

我已经尝试过 SnapshotChanges() 和 StateChange() 但没有成功。

我注意到当我添加一个帖子时,订阅被触发了两次:

时间线变化(2)timeline.ts:89:8

Addpost.ts 代码:

pushNewPost(url) {
    this.angularDb.list('timeline').push({
      dateCreated: new Date().toString(),
      postBy: firebase.auth().currentUser.uid,
      postText: this.postText,
      image: url
    })
}

使用的版本: “@angular”:“5.2.11”, "angularfire2":"^5.0.0-rc.5-next",

对此有什么想法吗?我错过了什么吗?

提前致谢

更新:

我找到了一个对渲染性能影响较小的替代方案(在视图上),但在数据库上触发更改时仍会加载所有数据:

    getTimeline() {
    console.log('Get timeline data');
    this.loadingProvider.show();
    this.createUserData();
    this.dataProvider.getCurrentUser().valueChanges().subscribe((user) => {
      this.user = <any>user;
    });

    this.getUserFriends().then((friends) => {
      this.friends = friends;
    }).then(() => {
      this.dataProvider.getTimelinePost().snapshotChanges().map(actions =>
        actions.map(a => ({ type: a.type, ...a.payload.val() }))
      ).subscribe(items => {
        this.checkPostData(items);
        this.loadingProvider.hide();
      });
    });
  }

  getUserFriends() {
    return new Promise((resolve) => {
      this.dataProvider.getFriends().valueChanges().subscribe((f) => {
        resolve(f)
      });
    });
  }

  checkPostData(currentPost) {
   // Check if whe have a child added to the timeline
    if (this.timelineLoaded === true) {
      currentPost.forEach(p => {
        if (p.type === 'child_added') {
          this.mapPost(p);
        }
      });
    } else {
      this.timelineData = [];
      currentPost.map(p => {
        return this.mapPost(p);
      });
    }
  }

  mapPost(currentPost) {
    let postedByFriend = this.checkIfPostedByFriend(currentPost);
    if (postedByFriend || currentPost.postBy === firebase.auth().currentUser.uid) {
      this.dataProvider.getUser(currentPost.postBy).valueChanges().subscribe((user) => {
        currentPost.avatar = user.img;
        currentPost.name = user.name
      });
      //  Liked ?
      this.dataProvider.getLike(currentPost.key).valueChanges().subscribe((likes) => {
        currentPost.likes = likes.length;
        currentPost.isLike = this.checkIfPostIsLiked(likes);
      });
      //  Disliked ?
      this.dataProvider.getdisLike(currentPost.key).valueChanges().subscribe((dislikes) => {
        currentPost.dislikes = dislikes.length;
        currentPost.isdisLike = this.checkIfPostIsDislike(dislikes);
      });
      // Commented ?
      this.dataProvider.getComments(currentPost.key).valueChanges().subscribe((comments) => {
        currentPost.comments = comments.length;
        currentPost.isComment = this.checkIfPostIsCommented(comments);
      });
      this.timelineData.unshift(currentPost);
      this.timelineLoaded = true
    }
    return currentPost;
  }

【问题讨论】:

    标签: angular firebase rxjs ionic3 angularfire2


    【解决方案1】:

    根据我的经验,没有简便的方法可以做到这一点。

    但由于它是一个时间线,列表更改将始终是子添加。你可以这样做:

      this.timelineData = [];
    
      this.dataProvider.getTimelinePost().valueChanges().subscribe((posts) => {
        posts.forEach(p => {
          if (!this.timelineData.find(data => data.dateCreated === p.dateCreated)) {
            const elem = this.checkPostData(p);
    
            // this will put it in front of the list, don't need to reverse
            this.timelineData.unshift(elem);
          }
        })
      });
    

    顺便说一句,您不应该在订阅中进行订阅,也不应该订阅来创建承诺。

    让我们考虑一下:

    const obs1$ = afDb.object(ANYREF).valueChanges();
    
    obs1$.subscribe(data => {
      const obs2$ = afDb.object(data).valueChanges();
      obs2$.subscribe(a => console.log(a));
    });
    

    这会起作用,但只要 obs1$ 触发,obs2$ 就会被“重新”订阅。

    ie:如果 obs1$ 有 5 次更改,您将有 5 个 obs2$' 订阅。 每次新的 obs2$ 更改,您将有 5 倍的 console.log...,这是内存泄漏

    可能使用 take(1) 运算符进行修复。这可以强制订阅被触发一次。

    const obs1$ = afDb.object(ANYREF).valueChanges();
    
    obs1$.subscribe(data => {
      const obs2$ = afDb.object(data).valueChanges();
    
      // Rxjs 5 way :
      obs2$.take(1).subscribe(a => console.log(a));
      // Rxjs 6 way :
      obs2$.pipe(take(1)).subscribe(a => console.log(a));
    });
    

    obs2$ 将始终被触发一次,不会发生内存泄漏,但您不会使用 obs2$ 获得“实时数据库数据”。

    更好的方法:

    const obs1$ = afDb.object(ANYREF).valueChanges();
    
    // Rxjs 5 way :
    obs1$.switchMap(data => afDb.object(data).valueChanges())
      .subscribe(a => console.log(a));
    // Rxjs 6 way :
    obs1$.pipe(switchMap(data => afDb.object(data).valueChanges()))
      .subscribe(a => console.log(a));
    

    使用此方法,您将保持两个可观察对象“实时绑定”到您的数据库而不会泄漏。 (创建的除外,因为它永远不会取消订阅)。

    你可以这样做:

    // ionViewWillEnter
    const obs1$ = afDb.object(ANYREF).valueChanges();
    this.sub = obs1$.pipe(switchMap(data => afDb.object(data).valueChanges()))
      .subscribe(a => console.log(a));
    
    // ionViewWillLeave
    this.sub.unsubscribe();
    

    如果您不这样做,您的订阅将保持有效,只要您不在同一页面上。

    您的代码对我来说似乎很复杂,但如果您是“示例学习者”,您可以参考此文档:https://www.learnrxjs.io/

    【讨论】:

    • 您好,非常感谢您的回答。我必须在加载用户的朋友后加载时间线数据,这就是我在 getUserFriends() 方法中做出承诺的原因。我认为我的时间线数据没有多个订阅,但我明白为什么 console.log 被触发两次,这是因为时间线数据收到一个“child_added”事件和一个“child_changed”(奇怪的行为 imo)。我将修改代码以便能够撤回订阅。
    • 这就是管道 observables 的全部意义所在。你可以像承诺一样链接它们。顺便说一句,如果您真的想将 observable 转换为 promise,那么还有运算符 .toPromise() (与“endable” observable => 您需要在您的情况下执行 obs$.take(1).toPromise() )跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    • 2023-01-30
    • 1970-01-01
    相关资源
    最近更新 更多