Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular/RXJS, How do I sort an Observable Array of objects based on a SPECIFIC String value?

I have an interesting problem (I think) and would love some help.

I am returning an Observable[DisplayCard[]] that is filtered based on a value. That all works great. After I filter that array based on a value, I NOW want to sort the returned array by another one of it's values called category. category can only be one of three String values: ONGOING, UPCOMING, and 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.

Here is my code:

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))),
    );
like image 300
Donny groezinger Avatar asked Mar 02 '23 10:03

Donny groezinger


2 Answers

You can achieve this by creating an enum to represent all possible values for CardCategory and defining the order they should be sorted in using Record<CardCategory, number>:

export enum CardCategory {
  OnGoing  = 'ONGOING',
  Upcoming = 'UPCOMING',
  Past     = 'PAST',
}

export const CategorySortOrder: Record<CardCategory, number> = {
  'ONGOING'  : 0,
  'UPCOMING' : 1,
  'PAST'     : 2
}

Then create a sort function that uses that to sort:

function sortByCardCategory(a: DisplayCard, b: DisplayCard) {  
  if(a.category === b.category) return 0;
  return CategorySortOrder[a.category] > CategorySortOrder[b.category] ? 1 : -1;
}

You can now easily sort like this:

displayCommunityCards$ = this.cardService.displayCards$.pipe(
  map(cards => cards
    .filter(c => c.type === CardType.CommunityEvent)
    .sort(sortByCardCategory)
  )
);

Here's a working StackBlitz demo.


Using the Record type isn't completely necessary, but it does guarantee that you define a sort order for all values of CardCategory. For example, if you added a new CardCategory, typescript will throw an error about CategorySortOrder not defining all values of CardCategory.

screenshot of error

Property 'MYNEWCAT' is missing in type
'{ 
  UPCOMING: number; 
  ONGOING: number; 
  PAST: number; 
}' 
but required in type 'Record<CardCategory, number>'. (2741)
like image 116
BizzyBob Avatar answered Mar 03 '23 23:03

BizzyBob


You can simply do this by creating three array and merge them in order. It can be done in 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]);

In the above snippet the memory used will be more.

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.

Another interesting way could be creating keys in an object with "ongoing", "upcoming" and "past". You can create an array as value for each and push things once you find it. You need to check when to start show the upcoming events and when to start past.

 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);

When you are done, just take the key and throw the data to the view.

like image 37
Apoorva Chikara Avatar answered Mar 04 '23 00:03

Apoorva Chikara