Как стать автором
Обновить

Ускоряем рендеринг MatTable за несколько шагов

Время на прочтение3 мин
Количество просмотров1.8K
Часто бывает так, что большую часть приложения составляют различные списки и таблицы. Чтобы каждый раз не изобретать велосипед, я как и многие, чаще использовал таблицы Angular Material.

Позже эти таблицы разрастались, в ячейки помещались как текстовые данные, так и вложенные элементы. Все это растет и становится неплохой нагрузкой на машину пользователя. И понятное дело это никому не нравится.

В моем последнем домашнем проекте была таблица, ячейки которой в основном были заполнены различными полями (можно даже сказать, что это была одна большая форма).

И рендеринг её занимал около 8 секунд (таблица 40 x 40).

Так как можно оптимизировать MatTable для больших списков?

image

Тестовый пример


Для того, чтобы помочь остальным разобраться с этой проблемой я написал небольшое тестовое приложение. Все та же таблица MatTable, с пятью колонками (первая — id элемента, остальные — обычные текстовые поля MatFormField).



Казалось бы, простая таблица, однако даже рендеринг 100 сток такой таблицы занимает ни много, ни мало 2 секунды (Ох уж этот материал и его «топовая» производительность).



Сам рендеринг в данном случае занял только (860 мс), однако на все 2.2 секунды страница зависла и пользователь расстроился.

Что же, давайте попробуем, отрендерить таблицу в которой будет 300 строк. И барабанная дробь, немного ждем и видим, что на скриптинг и рендеринг в сумме было потрачено почти 10 секунд. То-есть в случае, когда пользователь захочет вывести такую таблицу в 300 строк, его страница зависнет на 10 секунд. Это очень страшно и удивительно (на самом деле нет).



На самом деле я ещё пытался сделать замер времени, которое потребуется для отрисовки 1000 элементов, однако мой жалкий i7 не выдерживал и страница постоянно отваливалась.
Попытаемся сделать это позже с уже примененным решением.

Решение проблемы


И так, делать замеры с помощью браузерных утилит может каждый, а вот решить эту проблему нет.

К этому решению меня привело рассуждение над самой сутью проблемы.
правильное понимание проблемы, это уже как минимум половина ее решения.
Я думаю каждому понятно, что это происходит потому что данные таблицы сначала обрабатываются скриптами, а потом выплёвываются одним куском. В свою очередь именно поэтому мы и видим характерное подвисание.

Проблема найдена. Теперь остается придумать, как ее решить. Самым первым на ум приходит самое простое решение. Если проблема в обработке всех данных сразу, то нужно заставить таблицу обрабатывать данные частями.

Но как это сделать?

Давайте подумаем, что у нас для этого есть из-под коробки. Первым на ум приходит Track By функция. При изменение датасоурса не будет перерендивариватья всю таблица, а только ее изменения.

Давайте добавим эту функцию в нашу таблицу:

<mat-table [trackBy]="trackByFn" [dataSource]="commonDataSource">

Хорошо, но ведь у нас нет такого условия, что данные таблицы как-то изменяются, да и вообще не об этом сейчас разговор. Но что, если написать Pipe, который при инициализции data source будет разбивать данные и отдавать их таблице порционно. В свою очередь функция trackBy поможет избежать полного ререндера.

@Pipe({
  name: 'splitSchedule'
})
export class SplitPipe implements PipeTransform {
  public transform(value: any, takeBy: number = 4, throttleTime: number = 40): Observable<Array<any>> {
    return Array.isArray(value)
      ? this.getSplittedThread(value, takeBy, throttleTime)
      : of(value);
  }

  private getSplittedThread(data: Array<any>, takeBy: number, throttleTime: number): Observable<Array<any>> {
    const repeatNumber = Math.ceil(data.length / takeBy);
    return timer(0, throttleTime).pipe(
      map((current) => data.slice(0, takeBy * ++current)),
      take(repeatNumber)
    );
  }
}

Вот такой небольшой кусок кода, поможет вашему железу рендерить такие большие таблицы.

Применим этот pipe к нашему data source.


<mat-table [trackBy]="trackByFn"
           [dataSource]="commonDataSource | splitSchedule | async">

Теперь попробуем провести замеры. Рендерим 100, 300 и 1000 элементов.







И что мы видим? Что на самом успех не такой как мы ожидали:

  • 300 элементов рендерились на 1 секунду быстрее
  • 1000 отрендерерилось за 11 секунд, и вкладка не умерла
  • а 100 элементов вообще рендерелись на 150 мс дольше

Но не спешите делать выводы, давайте для начала посмотрим на поведение страницы в обоих случаях.





Как видно, в обычном случае страница зависает на несколько секунд и пользователь ничего не может сделать в этот момент, тогда как использование нашей пайпы с связке с trackBy дает пользователю практически мгновенную инициализацию таблицы и не вызывает никакого дискомфорта во время использования приложения.

Надеюсь кому-нибудь поможет эта статья.

Исходники тестового приложения есть на Stack Blitz.
Теги:
Хабы:
Всего голосов 3: ↑3 и ↓0+3
Комментарии1

Публикации