#angular #typescript
#angular #typescript
Вопрос:
У меня ситуация с двумя компонентами: родительский компонент (aComponent) передает данные, состоящие из массива с объектами, своему дочернему компоненту (BComponent), который отображает эти данные в элементе таблицы. Пока rows
не определено, в таблице будет отображаться индикация загрузки. Ниже приведено очень простое воссоздание моей ситуации.
import data from '../../services/data.json';
@Component({
selector: 'app-a',
templateUrl: `
<app-b [columns]="columns" [rows]="rows"></app-b>
`,
styleUrls: ['./a.component.scss']
})
export class AComponent implements OnInit {
columns: string[];
rows: any[];
constructor() { }
ngOnInit(): void {
this.columns = ['title', 'year', 'cast', 'genres'];
this.fetchData().then((res: any[]) => this.rows = res);
}
fetchData = (): Promise<any[]> => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data);
}, 1000);
});
}
}
@Component({
selector: 'app-b',
templateUrl: `
<table>
<tr>
<td *ngFor="let column of columns">{{column}}</td>
</tr>
<tr *ngIf="!rows">
<td [colSpan]="columns.length">Loading...</td>
</tr>
<ng-container *ngIf="rows">
<tr *ngFor="let row of rows">
<td>{{row.title}}</td>
<td>{{row.year}}</td>
<td>{{row.cast}}</td>
<td>{{row.genres}}</td>
</tr>
</ng-container>
</table>
`,
styleUrls: ['./b.component.scss']
})
export class BComponent implements OnInit {
@Input() columns: string[];
@Input() rows: any[];
constructor() { }
ngOnInit(): void { }
Этот подход отлично работает, если данные извлекаются из API, таким образом, асинхронно. Я попытался имитировать вызов API с задержкой через fetchData
функцию. Тестовые данные получены из локального .файл json, который я получил от https://raw.githubusercontent.com/prust/wikipedia-movie-data/master/movies.json
Но проблема, с которой я сталкиваюсь, заключается в том, что я передаю локальные данные из aComponent в BComponent, которые не извлекаются из API и, следовательно, больше не выполняются асинхронные операции. Итак, мы заменяем
ngOnInit(): void {
this.columns = ['title', 'year', 'cast', 'genres'];
this.fetchData().then((res: any[]) => this.rows = res);
}
с помощью
ngOnInit(): void {
this.columns = ['title', 'year', 'cast', 'genres'];
this.rows = data;
}
It takes several seconds to render the view of BComponent due to the large amount of data which needs to be placed into the DOM and the entire view (AComponent BComponent) is only shown once the rendering is done. So during this time the user is seeing a blank screen.
Чего я хотел бы добиться, так это того, чтобы сначала отображались и отображались столбцы и индикация загрузки BComponent, а затем строки, заменяя индикацию загрузки, как только они также будут полностью отображены. В основном то же самое, что и при асинхронной выборке данных из API.
Я пробовал несколько вещей, но безуспешно:
Разделение BComponent на два отдельных компонента: один для столбцов и один для строк, что не работает, поскольку родительский компонент (aComponent) отображается только тогда, когда все дочерние компоненты полностью отображаются.
Я также пробовал многое с помощью крючков жизненного цикла Angular. Например, присвоение данных строкам в ngAfterViewInit
перехватчике:
ngAfterViewInit(): void {
this.rows = data;
}
Но это приводит к удалению ExpressionChangedAfterItHasBeenCheckedError
ошибки.
По сути, это то, чего я хочу достичь: назначить и отобразить строки после инициализации представления компонента (и, следовательно, его дочерних представлений). Но похоже, что инициализация и фактический рендеринг — это две разные вещи в Angular. Из того, что я заметил, рендеринг представлений начинается только после того, как Angular прошел все перехваты жизненного цикла. Был AfterViewRendered
бы полезен хук или что-то в этом роде. Я думаю, что все, что связано с крючками, не подходит для моей ситуации?
Я также попытался обернуть локальные данные в наблюдаемые или пообещать сделать их асинхронными:
ngOnInit(): void {
this.columns = ['title', 'year', 'cast', 'genres'];
Promise.resolve().then(() => this.rows = data);
}
Но это также не работает, если в нем нет задержки с помощью setTimeout
. Назначение локальных данных строкам должно «выскакивать» из фазы инициализации и рендеринга представления, чтобы получить желаемый результат, с которым я имею дело setTimeout
. Но я надеюсь, что есть менее грязный способ добиться этого.
У меня нет идей для этого… поэтому любая помощь или идеи для разных подходов очень ценятся.
Извините, если я что-то не так понял или недопонимал.
Спасибо за ваше время!
РЕДАКТИРОВАТЬ: в моей таблице реализована разбивка на страницы, но сама таблица является относительно тяжелым компонентом, что, я думаю, объясняет длительное время рендеринга. Слишком большой файл data.json в этом примере предназначен только для имитации длительного времени рендеринга таблицы.
Ответ №1:
подход 1: реализовать механизм подкачки, нет необходимости показывать тысячи строк одновременно! мы, люди, не можем обработать такой объем данных сразу.
подход 2. Реализация виртуальной прокрутки если вам нужно отобразить все данные сразу, идея состоит в том, чтобы отображать только видимые строки, а остальные строки отображаются по требованию при прокрутке вниз.
В этом ответе я подробно расскажу, как вы выполняете второй подход:
Убедитесь, что у вас установлен angular cdk, если он не установлен
npm i @angular/cdk
затем добавьте его в свой модуль приложения
import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
...
imports: [ ScrollingModule, ...]
})
export class AppModule {}
затем используйте его в своем шаблоне следующим образом
<table>
<tr>
<td *ngFor="let column of columns">{{column}}</td>
</tr>
<tr *ngIf="!rows">
<td [colSpan]="columns.length">Loading...</td>
</tr>
<cdk-virtual-scroll-viewport *ngIf="rows" itemSize="50">
<tr *cdkVirtualFor="let row of rows">
<td>{{row.title}}</td>
<td>{{row.year}}</td>
<td>{{row.cast}}</td>
<td>{{row.genres}}</td>
</tr>
</cdk-virtual-scroll-viewport>
</table>
Теперь у вас должна быть лучшая производительность при использовании виртуальной прокрутки
Комментарии:
1. В моей таблице реализована разбивка на страницы, но сама таблица является относительно тяжелым компонентом, что, я думаю, объясняет длительное время рендеринга. Слишком большой файл data.json в моем примере предназначен только для имитации длительного времени рендеринга таблицы.
2. каков размер вашей страницы, в этом случае у вас не должно возникнуть проблем с рендерингом, проблемы связаны с накладными расходами на ввод-вывод и синтаксическим анализом json при чтении вашего локального файла. поскольку вы визуализируете каждую страницу, у вас не должно быть проблем с визуализацией, ИМХО!