如何在角度上创建无限滚动

If you’ve ever had to deal with large lists in your web app, then you’re probably familiar with infinite scrolling lists. Infinite scrolling is a common UX solution to the problem of presenting a large list to your users. Think about the Facebook feed or the Twitter Timeline and how seamlessly new content just magically appears as you move down to the list.

如果您曾经不得不在Web应用程序中处理大型列表,那么您可能对无限滚动列表很熟悉。 无限滚动是一种常见的UX解决方案,可以解决向用户显示大量列表的问题。 考虑一下Facebook feed或Twitter时间轴,以及当您移至列表时,新内容如何无缝地神奇地呈现。

In this article, we’re going to build something similar: a fast, infinite scrolling list in Angular using the CDK library. The end result will be the following material list, which loads up more items when you scroll down to the end. And most importantly, is also fast using virtual scrolling.

在本文中,我们将构建类似的内容:使用CDK库在Angular中快速,无限地滚动列表。 最终结果将是以下物料清单,当您向下滚动到末尾时,它将装载更多的物料。 最重要的是,使用虚拟滚动也很快。

Image for post

Let’s get started then!

让我们开始吧!

设置我们的项目 (Setting up our project)

To setup our project, we first create a new Angular app and add Angular Material components to the same. This can be done by using the following commands.

要设置我们的项目,我们首先创建一个新的Angular应用并将Angular Material组件添加到该组件中。 可以通过使用以下命令来完成。

ng new angular-infinite scroll
ng add @angular/material

Then, let’s add our required modules to the app.module.ts file.

然后,让我们将所需的模块添加到app.module.ts文件中。

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
ScrollingModule,
MatToolbarModule,
MatListModule,
MatDividerModule,
MatButtonModule,
MatIconModule,
MatMenuModule,
MatProgressSpinnerModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

生成列表项 (Generating the list items)

We’re going to generate a random array to render our list using a simple loop. Our list will contain a title, some content and an image avatar. We declare a new listItems array in our app.component.ts file. Then, we create a function to add more items to our list.

我们将生成一个随机数组,以使用简单循环呈现列表。 我们的列表将包含标题,一些内容和图像头像。 我们在app.component.ts文件中声明一个新的listItems数组。 然后,我们创建一个函数以将更多项添加到列表中。

listItems = [];


fetchMore(): void {
  
    const images = ['IuLgi9PWETU', 'fIq0tET6llw', 'xcBWeU4ybqs', 'YW3F-C5e8SE', 'H90Af2TFqng'];
    
    const newItems = [];
    
    for (let i = 0; i < 20; i++) {
      const randomListNumber = Math.round(Math.random() * 100);
      const randomPhotoId = Math.round(Math.random() * 4);
      newItems.push({
        title: 'List Item ' + randomListNumber,
        content: 'This is some description of the list - item # ' + randomListNumber,
        image: `https://source.unsplash.com/${images[randomPhotoId]}/50x50`
      });
    }
    
    this.listItems = [...this.listItems, ...newItems];
}

The title, content and the image for each item are generated randomly. Images are selected from a list of Unsplash images. We use the spread operator syntax to append the new items to our existing list array.

每个项目的标题,内容和图像都是随机生成的。 从未启动图像列表中选择图像。 我们使用传播运算符语法将新项目追加到我们现有的列表数组中。

We call this function in our ngOnInit method to populate the list initially as well.

我们在ngOnInit方法中调用此函数,以初始填充列表。

角材料清单 (The Angular Material List)

For the template we use the Angular Material List component as follows.

对于模板,我们使用Angular Material List组件,如下所示。

<mat-list class="content">
    <mat-list-item *ngFor="let item of listItems">
      <img matListAvatar [src]="item.image">
      <h3 matLine> {{item.title}} </h3>
      <p matLine>
        <span> {{item.content}} </span>
      </p>
      <button mat-icon-button [matMenuTriggerFor]="itemMenu">
        <mat-icon>
          more_vert
        </mat-icon>
      </button>
      <mat-divider></mat-divider>
    </mat-list-item>
</mat-list>


<mat-menu #itemMenu="matMenu">
  <button mat-menu-item>
    Option 1
  </button>
  <button mat-menu-item>
    Option 2
  </button>
  <button mat-menu-item>
    Option 3
  </button>
</mat-menu>

I’ve also added an Angular Material Menu here on each item, so that we can see the performance degradation when we render a large list (more on that below!). For more about how to set up an Angular Material list and the menu, you can visit the Angular Material components official documentation.

我还在每个项目上添加了一个Angular Material菜单,以便在渲染较大列表时可以看到性能下降(更多内容在下面!)。 有关如何设置Angular Material列表和菜单的更多信息,您可以访问Angular Material组件官方文档

We also add a bit of styling to fix the list height. This way we always get a fixed number of items visible at one time.

我们还添加了一些样式来固定列表高度。 这样一来,我们总是可以同时看到固定数量的商品。

.content {
height: calc(100vh - 64px);
overflow: auto;
}

As a result, now we have a small 20-item list which scrolls nicely and has a menu icon with each item, which opens up an Angular Material menu.

结果,现在我们有一个20个项目的小列表,该列表可以很好地滚动,并且每个项目都有一个菜单图标,这将打开Angular Material菜单。

Image for post

无限滚动列表的性能问题 (Performance issues with infinite scrolling lists)

Before moving on to adding more functionality, we need to re assess the implications of having an infinite scrolling list in our app.

在继续添加更多功能之前,我们需要重新评估在应用程序中拥有无限滚动列表的含义。

Infinite scroll is a nice feature but it doesn’t come without its problems. One of the main problems is that as new content loads in the list, it will keep adding to your DOM and increasing the memory requirement.

无限滚动是一个不错的功能,但并非没有问题。 主要问题之一是,随着列表中加载新内容,它将不断添加到DOM中并增加内存需求。

To test this, I used the same list template above and simply initialized the code with a list with thousands of items to see how it affects performance. I kept an eye on the memory tab in Chrome Developer Tools and also observed the UI for any lags.

为了测试这一点,我使用了与上面相同的列表模板,并简单地使用包含数千个项目的列表初始化代码,以查看其如何影响性能。 我留意了Chrome开发者工具中的“内存”标签,并观察了用户界面中是否有任何滞后现象。

Both of these indicated worsening performance as I increased the initial list size. Here’s a snapshot of the heap memory when the list size was 5000 and a GIF showing the UI degradation for the menu opening.

当我增加初始列表大小时,这两个都表明性能下降。 这是列表大小为5000时堆内存的快照,还有一个GIF,显示菜单打开时UI的下降。

Image for post
Image for post
Noticeable delay in opening up the menu
菜单打开明显延迟

I know what you’re thinking! Who would load 5000 items?

我知道你在想什么! 谁可以装载5000件物品?

Well, granted it is an extreme case. But it is just an indication of the performance problems that can crop up with infinite lists. As the list items become more complex to render or the app has more UI elements to render, the performance degradation will start sooner and will not be pretty.

好吧,这是极端的情况。 但这只是性能问题的指示,这些性能问题可能会因无限列表而出现。 随着列表项的呈现变得越来越复杂,或者应用程序具有更多的UI元素要呈现,性能下降将更快开始并且不会很漂亮。

Therefore, we need a strategy or technique to deal with this problem. And virtual scrolling seems to be the answer to our prayers!

因此,我们需要一种策略或技术来解决这个问题。 虚拟滚动似乎是我们祈祷的答案!

输入虚拟滚动 (Enter virtual scrolling)

In simple words, virtual scrolling means only those items which are visible to the user at any time are rendered. Other items which are not visible are removed from the DOM. This is also how mobile phones render large lists because performance problems are especially visible on mobile devices with limited memory.

简而言之,虚拟滚动意味着仅呈现用户在任何时候都可见的那些项目。 其他不可见的项目将从DOM中删除。 这也是移动电话呈现大列表的方式,因为性能问题在内存有限的移动设备上尤其明显。

Fortunately for us, Angular CDK provides a virtual scroller component out of the box. Let’s use the same for our sample app!

对我们来说幸运的是,Angular CDK提供了一个现成的虚拟滚动器组件。 让我们将其用于示例应用程序!

添加CDK虚拟滚动条 (Adding the CDK virtual scroller)

To add the Angular CDK virtual scroller, we first need to include the ScrollingModule in our app.module.ts file.

要添加Angular CDK虚拟滚动条,我们首先需要在我们的app.module.ts文件中包括ScrollingModule

Then we just modify our app.component.html file like this.

然后,我们只需要像这样修改app.component.html文件。

<cdk-virtual-scroll-viewport #scroller itemSize="72" class="content">
  <mat-list>
    <ng-container *cdkVirtualFor="let item of listItems">
      <mat-list-item>
        ...
      </mat-list-item>
    </ng-container>
  </mat-list>
</cdk-virtual-scroll-viewport>

The Angular Material List code remains completely the same. All we did was to replace the parent container with cdk-virtual-scroll-viewport. And instead of *ngFor we’re using *cdkVirtualFor to loop through our list items.

角度材料清单代码保持完全相同。 我们所做的就是用cdk-virtual-scroll-viewport替换父容器。 而不是*ngFor我们使用*cdkVirtualFor循环通过我们的列表项。

The itemSize input is important here. Currently, the Angular CDK virtual scroller only supports fixed size items in its list. So here we’re just passing in the height of a single item in our list in pixels.

这里的itemSize输入很重要。 当前,Angular CDK虚拟滚动器在其列表中仅支持固定大小的项目。 因此,这里我们只是传入列表中单个项目的高度(以像素为单位)。

测试虚拟滚动 (Testing the virtual scrolling)

If you test now, you won’t feel any difference at all in the list. But behind the scenes, the virtual scroller is adding and removing items as you scroll up and down. To see this in action, you can use the Chrome Developer tools or similar tooling to inspect the list items in the HTML. The number of list items in the DOM at any time will always remain the same now, whether you have a list of 50 elements or 5000 elements!

如果您现在进行测试,则列表中不会有任何区别。 但是在后台,虚拟滚动器在您上下滚动时添加和删除了项目。 要查看实际效果,您可以使用Chrome开发者工具或类似工具检查HTML中的列表项。 现在,无论您有50个元素列表还是5000个元素列表,DOM中任何时候列表项的数量现在将始终保持不变!

Image for post

使用RxJS检测滚动结束 (Detecting the end of the scroll using RxJS)

To append new items to the list as the user scrolls to the bottom, we need to add some RxJS magic to our app. RxJS is a library which helps us work with asynchronous data streams. Since scrolling events are one example of a stream of data, we can leverage the power of RxJS to detect whether the user has reached the end of our list.

要在用户滚动到底部时将新项目添加到列表中,我们需要向我们的应用程序添加一些RxJS魔术。 RxJS是一个库,可以帮助我们处理异步数据流。 由于滚动事件是数据流的一个示例,因此我们可以利用RxJS的功能来检测用户是否已到达列表的末尾。

The Angular CDK scroll viewport provides us with an elementScrolled event which we can conveniently use to build an RxJS stream. We can get the virtual scroller instance from the template by adding a ViewChild decorator in our app.component.ts file.

Angular CDK滚动视口为我们提供了elementScrolled事件,我们可以方便地使用它来构建RxJS流。 通过在app.component.ts文件中添加ViewChild装饰器,我们可以从模板中获取虚拟滚动器实例。

@ViewChild('scroller') scroller: CdkVirtualScrollViewport;

Then, we build up a stream like the following.

然后,我们建立如下的流。

this.scroller.elementScrolled().pipe(
      map(() => this.scroller.measureScrollOffset('bottom')),
      pairwise(),
      filter(([y1, y2]) => (y2 < y1 && y2 < 140)),
      throttleTime(200)
).subscribe(() => {
      this.ngZone.run(() => {
        this.fetchMore();
      });
}

分解RxJS流 (Breaking down the RxJS stream)

Let’s go through the steps in this stream briefly. For each time the user scrolls the list

让我们简要地介绍一下此流中的步骤。 用户每次滚动列表

  1. We get the scroll offset in pixels from the bottom of the list. For this we use a method provided by the virtual scroller called measureScrollOffset

    我们从列表底部获得滚动偏移量(以像素为单位)。 为此,我们使用了由虚拟滚动器提供的称为measureScrollOffset

  2. We then use the pairwise operator to get this offset in pairs, so that we can see whether it is increasing or decreasing

    然后,我们使用成对运算符成对获取此偏移量,以便我们可以看到它是在增加还是在减少
  3. Then we add a filter to the stream and only allow it to continue when y2 < y1, i.e. the user is scrolling down and also when the offset is near to the bottom (less than 140 pixels i.e. two items in our list)

    然后,我们向流中添加一个过滤器,仅当y2 <y1时 (即用户向下滚动)以及偏移量接近底部 (小于140像素,即列表中的两项)时,才允许其继续运行

  4. Lastly, we add a throttleTime operator, so that we don’t get repeated scroll events and just one in 200 ms

    最后,我们添加了油门时间运算符,这样我们就不会重复滚动事件,而只有200毫秒的事件

Pretty cool, huh? It’s amazing how convenient RxJS is in converting asynchronous events into more meaningful events.

太酷了吧? RxJS将异步事件转换为更有意义的事件有多么方便,这真令人惊讶。

For a more detailed introduction to RxJS and its basic operators, you might find my article on the same below.

有关RxJS及其基本运算符的更详细介绍,您可以在下面的同一文章中找到我的文章。

Last, but not the least, we subscribe to this stream and call our fetchMore()function there. We do this inside an ngZone run function because the CDK virtual scroller runs outside the ngZone for performance reasons.

最后但并非最不重要的一点是,我们订阅此流并在其中调用fetchMore()函数。 我们在ngZone运行功能内执行此操作,因为出于性能原因,CDK虚拟滚动条在ngZone外部运行。

Great! If you test out now, you should see new items being added as soon as you reach the end of the scroll. Mission accomplished!

太好了 ! 如果现在进行测试,则应该在滚动结束时看到新添加的项目。 任务完成!

Image for post

最后一点:添加加载指示器 (Finishing touch: Add the loading indicator)

In a more typical use case, you’ll be getting more list items from an API call which can bring in a delay in the process. So from a UX point of view, it is good to have a small progress spinner as soon you reach the end of the scroll and your app is fetching new items.

在更典型的用例中,您将从API调用中获取更多列表项,这可能会导致流程延迟。 因此,从UX的角度来看,最好在到达滚动末尾且应用正在获取新项目时使用一个小的进度微调器。

Let’s add that to our app as a finishing touch!

让我们将其添加到我们的应用程序中作为画龙点睛!

First, we’ll add a loading boolean variable to represent whether data is being fetched. Then, well modify our fetching function to add a little delay using the timer function in RxJS (just to simulate network delay).

首先,我们将添加一个loading布尔变量,以表示是否正在获取数据。 然后,使用RxJS中的计时器函数来修改获取函数,以增加一些延迟(只是为了模拟网络延迟)。

this.loading = true;


timer(1000).subscribe(() => {
  this.loading = false;
  this.listItems = [...this.listItems, ...newItems];
});

Next, we simply add the Angular Material Progress Spinner and some styling to make it appear at the bottom of the list.

接下来,我们只需添加“ Angular Material Progress”微调器和一些样式,使其显示在列表的底部。

<mat-list>
   .......
   <div class="spinner-item">
      <mat-progress-spinner [mode]="'indeterminate'" [diameter]="50">     
      </mat-progress-spinner>
    </div>
</mat-list>
.spinner-item {
display: grid;
place-items: center;
margin-top: 10px;
}

And we’re done! If you run ng serve now, you’ll see a nice infinite scroll list, which keeps loading more items as you scroll and shows a loading icon as well!

我们完成了! 如果您现在运行ng serve ,您将看到一个漂亮的无限滚动列表,该列表在滚动时不断加载更多项目,并显示加载图标!

Image for post

结论 (Conclusion)

As you can see now, infinite scroll can add a bit of complexity to your Angular app. But in cases where you have to show large lists to the user and also keep your app fast and give good performance, it is almost necessary to use it. Angular CDK Virtual Scroller helps us out greatly in such cases.

如您现在所见,无限滚动可能会给Angular应用程序增加一点复杂性。 但是,在必须向用户显示大型列表并保持应用程序快速运行并提供良好性能的情况下,几乎有必要使用它。 在这种情况下,Angular CDK Virtual Scroller可以极大地帮助我们。

I recently shifted one of my client’s complex infinite list to use virtual scrolling and it has improved performance ‘tremendously’ in his own words. I hope it helps you in your own projects as well!

我最近将客户的一个复杂的无限列表中的一个转移到了使用虚拟滚动的功能,用他自己的话说,它“极大地”改善了性能。 希望它对您自己的项目也有帮助!

The full code for this tutorial can be found in the following GitHub repo.

可以在以下GitHub存储库中找到本教程的完整代码。

Thanks for reading! Bye :)

谢谢阅读! 再见:)

This story was originally published at zoaibkhan.com

这个故事最初发表在zoaibkhan.com

翻译自: https://medium.com/javascript-in-plain-english/how-to-create-infinite-scrolling-in-angular-145e3901a700

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值