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

Time:10-24

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

CodePudding user response:

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]);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

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);
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

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

CodePudding user response:

While the other answer works, you don't need to create multiple arrays or even subscribe to achieve this. You are always better of just using async pipes in your template and providing the sorting function to use on the result of the async pipes.

The Enum & Interface used in this example:

enum Cat {
  ONGOING = 'ONGOING',
  UPCOMING = 'UPCOMING',
  PAST = 'PAST',
}

interface DisplayCommunityCards {
  category: Cat;
  type: string;
}

In your component:

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$ is just a replacement for the observable that you are getting from your service.)

In your template:

<p *ngFor="let c of sort(displayCommunityCards$ | async)" >
  {{ c.category }}
</p>

By doing it this way you will not have to worry about unsubscibing in an onDestroy and you are able to use the OnPush ChangeDetectionStrategy.

Here is an example on Stackblitz.

  • Related