Angular/RXJS,如何根据特定字符串值对对象的 Observable 数组进行排序?
Angular/RXJS, How do I sort an Observable Array of objects based on a SPECIFIC String value?
我有一个有趣的问题(我认为),希望得到一些帮助。
我正在 returning 一个基于值过滤的 Observable[DisplayCard[]]。这一切都很好。在我根据一个值过滤该数组后,我现在想按另一个名为 category
的值对 returned 数组进行排序。 category
只能是以下三个字符串值之一:ONGOING
、UPCOMING
和 PAST
.
我想要 return 一个排序如下的数组: 进行中 --> 即将进行 --> 过去。所以,所有有“ONGOING”的显示卡都在前面,在没有更多这些之后,我想要所有“即将到来”的卡片,然后在那些之后,我想要显示所有“PAST”卡片。
这是我的代码:
displayCommunityCards$ = this.cardService.displayCards$
.pipe(
map(communityEventsCards => communityEventsCards.filter(community => community.type === 'COMMUNITY EVENT')
.sort((a, b) => {
return ???
}),
tap(data => console.log('Community Cards: ', JSON.stringify(data))),
);
您可以通过创建三个数组并按顺序合并它们来简单地做到这一点。它可以在 O(n) 中完成。
const upcoming = [],
past = [],
ongoing = [];
const events = [{event: "upcoming", id: 1}, {event: "upcoming", id: 2}, {event: "past", id: 3}, {event: "past", id: 4}, {event: "ongoing", id: 5}];
events.forEach(el => {
el.event === "upcoming" ? upcoming.push(el) :
el.event === "past" ? past.push(el) :
el.event === "ongoing" ? ongoing.push(el) : false;
});
console.log([...ongoing, ...upcoming, ...past]);
在上面的代码片段中,使用的内存会更多。
I want to return an array that will sort like this: ONGOING -->
UPCOMING --> PAST. So, all the display cards that have "ONGOING" will
be in front, after there is no more of those, I want all the
"UPCOMING" cards and then after those, I want all the "PAST" cards to
show.
另一种有趣的方法是在对象中创建具有“进行中”、“即将到来”和“过去”的键。您可以创建一个数组作为每个数组的值,并在找到它后推送内容。您需要检查何时开始显示即将发生的事件以及何时开始显示过去。
const result = {
upcoming: [],
past: [],
ongoing: []
}
const events = [{event: "upcoming", id: 1}, {event: "upcoming", id: 2}, {event: "past", id: 3}, {event: "past", id: 4}, {event: "ongoing", id: 5}];
events.forEach(el => {
el.event === "upcoming" ? result.upcoming.push(el) :
el.event === "past" ? result.past.push(el) :
el.event === "ongoing" ? result.ongoing.push(el) : false;
});
console.log(result);
大功告成,拿着钥匙,把数据丢给视图就行了。
虽然其他答案有效,但您无需创建多个数组甚至订阅即可实现此目的。您最好在模板中使用 async pipes 并提供 sorting function 以用于异步管道的结果。
本例中使用的Enum & 接口:
enum Cat {
ONGOING = 'ONGOING',
UPCOMING = 'UPCOMING',
PAST = 'PAST',
}
interface DisplayCommunityCards {
category: Cat;
type: string;
}
在你的组件中:
cards$ = of([
{ category: Cat.ONGOING, type: 'COMMUNITY EVENT' },
{ category: Cat.PAST, type: 'COMMUNITY EVENT' },
{ category: Cat.PAST, type: 'COMMUNITY EVENT' },
{ category: Cat.UPCOMING, type: 'COMMUNITY EVENT' },
]);
displayCommunityCards$ = this.cards$.pipe(
map((communityEventsCards) =>
communityEventsCards.filter(
(community) => community.type === 'COMMUNITY EVENT'
)
)
);
sort(
displayCommunityCards: DisplayCommunityCards[]
): DisplayCommunityCards[] {
const catOrder = Object.values(Cat);
return displayCommunityCards.sort((a, b) => {
return catOrder.indexOf(a.category) - catOrder.indexOf(b.category);
});
}
(cards$ 只是您从服务中获得的可观察对象的替代品。)
在您的模板中:
<p *ngFor="let c of sort(displayCommunityCards$ | async)" >
{{ c.category }}
</p>
通过这种方式,您将不必担心在 onDestroy 中取消订阅,并且您可以使用 OnPush ChangeDetectionStrategy。
这是 Stackblitz 上的示例。
您可以通过创建一个枚举来表示 CardCategory
的所有可能值并使用 Record<CardCategory, number>
:
定义它们的排序顺序来实现这一点
export enum CardCategory {
OnGoing = 'ONGOING',
Upcoming = 'UPCOMING',
Past = 'PAST',
}
export const CategorySortOrder: Record<CardCategory, number> = {
'ONGOING' : 0,
'UPCOMING' : 1,
'PAST' : 2
}
然后创建一个使用它进行排序的排序函数:
function sortByCardCategory(a: DisplayCard, b: DisplayCard) {
if(a.category === b.category) return 0;
return CategorySortOrder[a.category] > CategorySortOrder[b.category] ? 1 : -1;
}
您现在可以像这样轻松排序:
displayCommunityCards$ = this.cardService.displayCards$.pipe(
map(cards => cards
.filter(c => c.type === CardType.CommunityEvent)
.sort(sortByCardCategory)
)
);
这是一个有效的 StackBlitz 演示。
使用 Record
类型并非完全必要,但它确实可以保证您为 CardCategory
的所有值定义排序顺序。例如,如果你添加了一个新的 CardCategory
,typescript 将抛出一个关于 CategorySortOrder
not defining all values of CardCategory
.
的错误
Property 'MYNEWCAT' is missing in type
'{
UPCOMING: number;
ONGOING: number;
PAST: number;
}'
but required in type 'Record<CardCategory, number>'. (2741)
我有一个有趣的问题(我认为),希望得到一些帮助。
我正在 returning 一个基于值过滤的 Observable[DisplayCard[]]。这一切都很好。在我根据一个值过滤该数组后,我现在想按另一个名为 category
的值对 returned 数组进行排序。 category
只能是以下三个字符串值之一:ONGOING
、UPCOMING
和 PAST
.
我想要 return 一个排序如下的数组: 进行中 --> 即将进行 --> 过去。所以,所有有“ONGOING”的显示卡都在前面,在没有更多这些之后,我想要所有“即将到来”的卡片,然后在那些之后,我想要显示所有“PAST”卡片。
这是我的代码:
displayCommunityCards$ = this.cardService.displayCards$
.pipe(
map(communityEventsCards => communityEventsCards.filter(community => community.type === 'COMMUNITY EVENT')
.sort((a, b) => {
return ???
}),
tap(data => console.log('Community Cards: ', JSON.stringify(data))),
);
您可以通过创建三个数组并按顺序合并它们来简单地做到这一点。它可以在 O(n) 中完成。
const upcoming = [],
past = [],
ongoing = [];
const events = [{event: "upcoming", id: 1}, {event: "upcoming", id: 2}, {event: "past", id: 3}, {event: "past", id: 4}, {event: "ongoing", id: 5}];
events.forEach(el => {
el.event === "upcoming" ? upcoming.push(el) :
el.event === "past" ? past.push(el) :
el.event === "ongoing" ? ongoing.push(el) : false;
});
console.log([...ongoing, ...upcoming, ...past]);
在上面的代码片段中,使用的内存会更多。
I want to return an array that will sort like this: ONGOING --> UPCOMING --> PAST. So, all the display cards that have "ONGOING" will be in front, after there is no more of those, I want all the "UPCOMING" cards and then after those, I want all the "PAST" cards to show.
另一种有趣的方法是在对象中创建具有“进行中”、“即将到来”和“过去”的键。您可以创建一个数组作为每个数组的值,并在找到它后推送内容。您需要检查何时开始显示即将发生的事件以及何时开始显示过去。
const result = {
upcoming: [],
past: [],
ongoing: []
}
const events = [{event: "upcoming", id: 1}, {event: "upcoming", id: 2}, {event: "past", id: 3}, {event: "past", id: 4}, {event: "ongoing", id: 5}];
events.forEach(el => {
el.event === "upcoming" ? result.upcoming.push(el) :
el.event === "past" ? result.past.push(el) :
el.event === "ongoing" ? result.ongoing.push(el) : false;
});
console.log(result);
大功告成,拿着钥匙,把数据丢给视图就行了。
虽然其他答案有效,但您无需创建多个数组甚至订阅即可实现此目的。您最好在模板中使用 async pipes 并提供 sorting function 以用于异步管道的结果。
本例中使用的Enum & 接口:
enum Cat {
ONGOING = 'ONGOING',
UPCOMING = 'UPCOMING',
PAST = 'PAST',
}
interface DisplayCommunityCards {
category: Cat;
type: string;
}
在你的组件中:
cards$ = of([
{ category: Cat.ONGOING, type: 'COMMUNITY EVENT' },
{ category: Cat.PAST, type: 'COMMUNITY EVENT' },
{ category: Cat.PAST, type: 'COMMUNITY EVENT' },
{ category: Cat.UPCOMING, type: 'COMMUNITY EVENT' },
]);
displayCommunityCards$ = this.cards$.pipe(
map((communityEventsCards) =>
communityEventsCards.filter(
(community) => community.type === 'COMMUNITY EVENT'
)
)
);
sort(
displayCommunityCards: DisplayCommunityCards[]
): DisplayCommunityCards[] {
const catOrder = Object.values(Cat);
return displayCommunityCards.sort((a, b) => {
return catOrder.indexOf(a.category) - catOrder.indexOf(b.category);
});
}
(cards$ 只是您从服务中获得的可观察对象的替代品。)
在您的模板中:
<p *ngFor="let c of sort(displayCommunityCards$ | async)" >
{{ c.category }}
</p>
通过这种方式,您将不必担心在 onDestroy 中取消订阅,并且您可以使用 OnPush ChangeDetectionStrategy。
这是 Stackblitz 上的示例。
您可以通过创建一个枚举来表示 CardCategory
的所有可能值并使用 Record<CardCategory, number>
:
export enum CardCategory {
OnGoing = 'ONGOING',
Upcoming = 'UPCOMING',
Past = 'PAST',
}
export const CategorySortOrder: Record<CardCategory, number> = {
'ONGOING' : 0,
'UPCOMING' : 1,
'PAST' : 2
}
然后创建一个使用它进行排序的排序函数:
function sortByCardCategory(a: DisplayCard, b: DisplayCard) {
if(a.category === b.category) return 0;
return CategorySortOrder[a.category] > CategorySortOrder[b.category] ? 1 : -1;
}
您现在可以像这样轻松排序:
displayCommunityCards$ = this.cardService.displayCards$.pipe(
map(cards => cards
.filter(c => c.type === CardType.CommunityEvent)
.sort(sortByCardCategory)
)
);
这是一个有效的 StackBlitz 演示。
使用 Record
类型并非完全必要,但它确实可以保证您为 CardCategory
的所有值定义排序顺序。例如,如果你添加了一个新的 CardCategory
,typescript 将抛出一个关于 CategorySortOrder
not defining all values of CardCategory
.
Property 'MYNEWCAT' is missing in type
'{
UPCOMING: number;
ONGOING: number;
PAST: number;
}'
but required in type 'Record<CardCategory, number>'. (2741)