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 只能是以下三个字符串值之一:ONGOINGUPCOMINGPAST.

我想要 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)