繁体   English   中英

使用rxjs对添加到Firestore集合的自定义属性进行排序/分组

[英]Using rxjs to sort / group on a custom property added to a Firestore collection

我在Firestore集合中有一些评论,使用以下扁平结构:

export interface Comment {
  postId: string;
  inReplyTo: string;
  user: string;
  comment: string;
  timestamp: firebase.firestore.FieldValue;
}

以下是一些简化的示例数据:

[
  {commentId: 'A', inReplyTo: '', timestamp: '2018-03-22'},
  {commentId: 'B', inReplyTo: '', timestamp: '2018-01-15'},
  {commentId: 'C', inReplyTo: 'B', timestamp: '2018-04-14'},
  {commentId: 'D', inReplyTo: 'A', timestamp: '2018-07-08'},
]

注释不打算深入/递归地使用。 只有顶级的评论和答复。

我像这样查询Firestore:

this._commentsCollection = this.afs.collection('comments', ref => ref.where('postId', '==', this.postId)
    .orderBy('timestamp'));

因为我需要注释的文档ID元数据,所以我先调用SnapshotChanges() ,然后再调用map()

this.comments = this._commentsCollection.snapshotChanges().pipe(
    map(actions => {
        return actions.map(a => {
            const data = a.payload.doc.data();
            const id = a.payload.doc.id;

            var topLevelCommentId;
            if ( data.inReplyTo == '' ) {
                topLevelCommentId = id;
            } else {
                topLevelCommentId = data.inReplyTo;
            }

            return { id, topLevelCommentId, ...data };
        });
    })
);

您会看到,我还向每个注释添加了topLevelCommentId属性。 我的目标是对该属性进行排序,以在模板中以正确的顺序显示注释。

我知道出于性能方面的考虑,我不应该在ngFor中使用管道。

因此,是否有可能改为使用RxJS按我想要的方式对它们进行分组和排序? 首先,我想按时间戳对顶级评论进行排序。 然后,我想将它们全部按topLevelCommentId分组。 接下来,我想按inReplyTo对每个组进行排序,以便得到这样的顺序:

[
  {commentId: 'B', inReplyTo: '', timestamp: '2018-01-15'},
  {commentId: 'C', inReplyTo: 'B', timestamp: '2018-04-14'},
  {commentId: 'A', inReplyTo: '', timestamp: '2018-03-22'},
  {commentId: 'D', inReplyTo: 'A', timestamp: '2018-07-08'},
]

在SQL中,我可以说类似ORDER BY时间戳,topLevelCommentId,inReplyTo =''之类的东西,它将对它们进行正确排序。

在Javascript中,这样做也应该非常简单-如果我正在处理普通数组。 它是一种流,除了它是流式的。

我不知道该怎么办,是要挂接到上面的pipe()中,以按topLevelCommentId对数组进行排序。 就像,在该链接序列中,我的代码放在哪里? 并且,是否有任何RxJS运算符会让我更轻松?

我已经看过两个相关的问题,它们没有回答我的问题。 我不能使用这个,因为它迫使我在模板中嵌套* ngFor异步循环,这是行不通的。 另一个人更有希望,但是给出的答案对我不起作用。 我收到错误消息Property 'groupBy' does not exist on type 'any[]'.

对于任何建议或见解,我将不胜感激。

编辑:我只是意识到,只要通过groupBy运行它们, groupBy可以将它们按正确的顺序排序,假设时间戳记顺序是通过该操作保留的。 我的更大问题仍然存在。 我应该在上面的pipe()中将该代码放在哪里?

编辑2:我能够以其他方式解决此问题。 我没有尝试对可观察流进行排序,而是订阅了它,将其转换为普通的Javascript数组,然后在其上使用了标准Javascript排序方法。 我的模板现在使用ngFor而不是this.comments遍历this.sortedComments。 这是代码。 它可能不必要地冗长,但是我发现遵循这种方式更容易。 它也可能需要更多的计算,但是我没有处理大量的注释,因此对于我的目的来说已经足够了。

public sortedComments = [];

// load comments 
this._commentsCollection = this.afs.collection('comments', ref => ref.where('postId', '==', this.postId).orderBy('timestamp'));

// subscribe to the comments observable
this._commentsCollection.snapshotChanges().subscribe(rawData => {

  // convert the array of DocumentChangeAction elements to a normal array
  let normalArray = [];
  let allTopLevelIds = [];
  rawData.forEach( (a) => {

      const data = a.payload.doc.data();
      const id = a.payload.doc.id;

      // Add another property to indicate which top-level thread this
      // comment belongs to
      var topLevelCommentId;
      if ( data.inReplyTo == '' ) {
        topLevelCommentId = id;
      } else {
        topLevelCommentId = data.inReplyTo;
      }

      // Keep a record of all possible top-level IDs for use later
      if ( allTopLevelIds.indexOf(topLevelCommentId) == -1 ) {
        allTopLevelIds.push(topLevelCommentId);
      }

      const thisObj = { id, topLevelCommentId, ...data }

      // push this object onto the normalArray
      normalArray.push(thisObj);
  });

  // Loop over all toplevel Ids
  allTopLevelIds.forEach( (a) => {

    // Loop over all comments and find the actual top-level comment for this thread
    normalArray.forEach( (b) => {

      if ( b.id == a ) {
        this.sortedComments.push(b); // push the first comment in this thread onto the array

        // Loop over all comments again and find all replies to the top-level comment
        normalArray.forEach( (c) => {

          if ( ( c.id != a ) && ( c.inReplyTo == a ) ) {
            this.sortedComments.push(c); // push this reply onto the array
          }

        });

      }
    });

  });

});

实际上,您所需要做的就是执行嵌套排序,一切都很好。 无需使用groupBy (是的,与JS相比,SQL语法的命名略有不同),因为尽管您可以使用groupBy达到相同的结果,但是您需要将其展平为一个1维数组。

只需在管道中执行另一个.map() ,这一次使用Javascript的本机API中的.sort()

map((actions) => {
    return actions.sort((a, b) => {
        //first sort by timestamp
        if (a.timestamp > b.timestamp)
            return -1;
        if (b.timestamp > a.timestamp)
            return 1;

        // if timeStamp is the same, proceed to compare inReplyTo
        if (a.inReplyTo > b.inReplyTo)
            return -1;
        if (b.inReplyTo > a.inReplyTo)
            return 1;

        // if inReplyTo is the same, proceed to compare commentId
        if (a.commentId > b.commentId)
            return -1;
        if (b.commentId > a.commentId)
            return 1;
        return 0;
    })
})

这是一个非常简洁的代码,基本上与上面的代码相同:

this._commentsCollection.snapshotChanges()
    .pipe(
        map(actions => actions.map(a => ({
                data: a.payload.doc.data(),
                id: a.payload.doc.id,
                topLevelCommentId: a.payload.doc.data().inReplyTo === '' ? a.payload.doc.id : a.payload.doc.data(),
            })
        )),
        map((actions) => actions.sort((a, b) => {                            
                let timestampDiff = a.timestamp > b.timestamp;
                let inReplyToDiff = a.inReplyTo > b.inReplyTo;
                let commentIdDiff = a.commentId > b.commentId;

                return timestampDiff === 0 ? (inReplyToDiff === 0 ? commentIdDiff : inReplyToDiff) : timestampDiff;
            })
        )
    );

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM