[英]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.