angular - Angular:在渲染父组件后更改子组件数据
问题描述
我有两个组件的情况:父组件 (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 获取的,这种方法可以正常工作,因此是异步的。我试图通过该fetchData
函数模拟一个延迟的 API 调用。测试数据来自我从https://raw.githubusercontent.com/prust/wikipedia-movie-data/master/movies.json获得的本地 .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;
}
由于需要将大量数据放入 DOM 中,渲染 BComponent 的视图需要几秒钟的时间,并且整个视图(AComponent + BComponent)只有在渲染完成后才会显示。因此,在此期间,用户看到的是空白屏幕。
我想要实现的是首先渲染和显示 BComponent 的列和加载指示,然后是行,一旦它们也完全渲染,就替换加载指示。所以与从 API 异步获取数据时发生的情况基本相同。
我尝试了几件事但没有成功:
将 BComponent 拆分为两个单独的组件:一个用于列,一个用于行,这不起作用,因为父组件 (AComponent) 仅在所有子组件完全呈现时才会显示。
我还通过 Angular 生命周期钩子尝试了很多东西。例如将数据分配给ngAfterViewInit
钩子中的行:
ngAfterViewInit(): void {
this.rows = data;
}
但这会消除ExpressionChangedAfterItHasBeenCheckedError
错误。
这本质上是我想要实现的:在 AComponent 视图(以及它的子视图)初始化之后分配和渲染行。但在 Angular 中,初始化和实际渲染似乎是两件不同的事情。据我所知,视图的渲染只有在 Angular 完成所有生命周期钩子后才开始。一个钩子AfterViewRendered
或类似的东西会很方便。所以我认为任何带有钩子的东西都不适合我的情况?
我还尝试将本地数据包装在 Observables 或 Promise 中以使其异步:
ngOnInit(): void {
this.columns = ['title', 'year', 'cast', 'genres'];
Promise.resolve().then(() => this.rows = data);
}
但这也行不通,除非在setTimeout
. 将本地数据分配给行需要“跳出”视图初始化和渲染 fase 以获得我想要的结果,这就是setTimeout
. 但我希望有一种不那么肮脏的方式来实现这一目标。
我对这个没有想法......所以非常感谢任何关于不同方法的帮助或想法。
对不起,如果我有错误或误解。
谢谢你的时间!
编辑:我在我的表上实现了分页,但表本身是一个相对较重的组件,这解释了我认为的长渲染时间。本例中过大的data.json文件只是为了模仿表格渲染时间过长。
解决方案
方法一: 实现分页机制,无需一次显示上千行!我们人类无法一次处理这么多的数据。
方法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>
您现在应该使用虚拟滚动获得更好的性能
推荐阅读
- python - 为什么 pynput 不检测数字键盘按下?
- c++ - 无法弄清楚 C++ lamda 表达式的语法作为方法的参数
- android - 在 uncaughtException 上启动新活动
- ios - 自定义 UISlider 子视图框架从主视图偏移
- javascript - 我想为每个组件复制但分别维护状态。我是否需要分别为每个组件创建每个函数?
- python - Python/Tweepy 转发推特账户
- byte-buddy - ByteBuddy java 代理需要增加代理 jar 大小的应用程序依赖项
- c - JACK 语言的 Lexer 中的分段错误
- javascript - ASP.NET webForm 中 SQL 查询的 HighCharts 热图
- html - 如何在占位符中添加红色星号?