Как я могу отсортировать результат наблюдаемого несколькими способами?

#angular #typescript #rxjs

Вопрос:

У меня есть одержимый тип табличных данных, который возвращает мне набор строк и столбцов таблицы. Пользователь может выбрать из списка выбрать набор столбцов (к которым относятся строки) для сортировки (по умолчанию в порядке возрастания). Например, если пользователь вводит «сотрудник» и «проект», я должен сначала отсортировать строки по сотруднику и, в свою очередь, отсортировать проекты в порядке возрастания для каждого сотрудника.

тип класса:

 
export interface TableColumn {
  value: string;
  label: string;
  hidden: boolean;
  format: (value: any) => string;
}

export interface TableRow {
  [key: string]: any;
}

export interface TableData {
  columns: TableColumn[];
  rows: TableRow[];
}
export class Activity {
  id?: number;
  [HOURS_KEY]: number;
  [DATE_KEY]: string;
  [EMPLOYEE_KEY]: ActivityEmployee;
  [TYPE_KEY]: ActivityType;
  [PROJECT_KEY]?: ActivityProject;
}

export class ActivityEmployee {
  id: number;
  lastName: string;
  firstName: string;
}
 

Предполагая, что теперь я хотел отсортировать только по сотрудникам и проектам (так что это статический пример), как я могу упорядочить по цепочке все, что я получаю?

я называю навязчивый результат таким образом:

 
tableDataColumns$ = this.activityCollection.groupedEntities$?.pipe(
    map((e) => e.columns)
  );
  tableDataRows$ = this.activityCollection.groupedEntities$?.pipe(
    map((e) => e.rows)
  );
 

Ответ №1:

 sortBy = new Subject<[string, string]>();

tableDataRows$ = this.sortBy.pipe(
  startWith(["", ""]),
  switchMap(([sort1, sort2]) =>
    this.activityCollection.groupedEntities$.pipe(
      map(({ rows }) => rows),
      map(rows =>
        !!sort1 ? rows.sort((a, b) => (a[sort1] < b[sort1] ? -1 : 1)) : rows
      ),
      map(rows => (!!sort2 ? this.splitSortRows(rows, sort1, sort2) : rows))
    )
  )
);

splitSortRows = <T>(rows: T[], splitBy: string, sortBy: string): T[] => {
  const uniqueValues = [...new Set(rows.map(row => row[splitBy]))];
  const splitArray = uniqueValues.map(value =>
    rows.filter(row => row[splitBy] === value)
  );
  return splitArray.reduce(
    (acc, arrSegment) => [
      ...acc,
      ...arrSegment.sort((a, b) => a[sortBy] < b[sortBy] ? -1 : 1),
    ],
    [] as T[]
  );
};
 

Мы начинаем с объявления темы, которая будет выдавать кортеж, представляющий две метки для сортировки. Затем мы настроим нашу трубу в следующей последовательности:

  1. Подпишитесь на нашу тему, чтобы получать последние критерии сортировки
  2. Переключите карту на наши наблюдаемые исходные данные.
  3. Используйте map() с деконструкцией объектов, чтобы получить наши rows данные.
  4. Сортировка rows массива на основе наших первых критериев, выполненная с помощью простого .sort() .
  5. Затем мы вызываем более сложную функцию для выполнения субсортировки.

Что касается того, как splitSortRows() работает:

  1. Создайте массив уникальных значений строк по первому критерию сортировки.
  2. Для каждого уникального элемента мы разбиваем наш единый массив на несколько массивов. Каждая группа подмассивов имеет одинаковое значение наших первых критериев фильтрации.
  3. Затем мы сортируем каждый поддиапазон, а затем объединяем их обратно в один массив.

Теперь, когда ваш пользователь нажимает на одну или две метки для сортировки, вы можете указать эти метки сортировки в sortBy теме. Подписка позаботится обо всем остальном.

 this.sortBy.next(['employee', 'project']);
// tableDataRows$ should now emit a new array of rows sorted by employee first, and projects second
 

Комментарии:

1. спасибо, но у меня 5 столбцов(не все числовые, некоторые строковые), так что этот подход, возможно, слишком широк

2. Извини за это @Man24. Я обновил свой ответ, чтобы он сортировал как числа, так и строки.