AngularFireList-避免在订阅更改时重新加载所有数据-Angular5 / Ionic3

时间:2018-07-06 09:41:27

标签: angular firebase rxjs ionic3 angularfire2

早上好

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

说明:

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

Timeline.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;
}

Timeline.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)时间轴。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;
  }

1 个答案:

答案 0 :(得分:0)

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

但是由于这是一个时间轴,列表更改将始终是子项添加。您可以执行以下操作:

  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/