{"id":28019,"date":"2021-04-07T20:12:36","date_gmt":"2021-04-07T20:12:36","guid":{"rendered":"http:\/\/How-to-use-virtual-scrolling-to-load-images-in-Angular"},"modified":"2025-02-16T12:57:11","modified_gmt":"2025-02-16T20:57:11","slug":"how-to-use-virtual-scrolling-to-load-images-in-angular","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/","title":{"rendered":"How to use virtual scrolling to load images in Angular"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Web Performance is something that is becoming more important every day for users and developers. When the user enters a website, they can get impatient if it does not load within a few seconds and eventually leave the site.<\/p>\n<blockquote>\n<\/blockquote>\n<p>Let\u2019s say you\u2019re developing a web application that involves the rendering of hundreds or even thousands of elements.<\/p>\n<p>In that kind of scenario, you may have to process a large set of data and the performance can be drastically affected due to the number of nodes that are being created in the DOM (the more data to display, the bigger the DOM tree).<\/p>\n<p>In this article, I\u2019ll explain a useful strategy to reduce the amount of rendered DOM nodes regardless of the size of your data set: <strong>Virtual Scrolling<\/strong><\/p>\n<h2>The Problem<\/h2>\n<p>Let\u2019s suppose you\u2019re building a web application that needs to render an image gallery of thousands of users. The app retrieves the images from a large set of data.<\/p>\n<p>However, it\u2019s required to display them within a defined area as the following screenshot shows.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1614709689\/e-60354e32ba3535006813ce00\/khnbqa4cktnietshe5u2.jpg\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1398\"\/><\/p>\n<p>Given that scenario, it\u2019s logical to think that it does not make much sense to render all the elements at the same time, but only the necessary ones.<\/p>\n<h2>Virtual Scrolling<\/h2>\n<p>Most popular frameworks have an implementation for virtualized content. Also, it\u2019s worth mentioning the <a href=\"https:\/\/github.com\/WICG\/virtual-scroller\/tree\/master\/study-group\">infinite list study group<\/a> that covers research over virtualization solutions for the web and other platforms.<\/p>\n<p>In a context of a Virtual Scroll implementation, the items will be removed or added as follows:<\/p>\n<ul>\n<li>It will delete the invisible rows while the user is going down through the scroll<\/li>\n<li>It will add new rows at runtime<\/li>\n<li>The <strong>View Port<\/strong> is the area that renders only the necessary elements<\/li>\n<\/ul>\n<h3>The Angular CDK Solution<\/h3>\n<p>The <a href=\"https:\/\/material.angular.io\/cdk\/categories\">Angular CDK<\/a> is a set of behavior primitives for building UI components. Also, the <a href=\"https:\/\/material.angular.io\/\">Angular Material components<\/a> have been defined using these utilities.<\/p>\n<p>You can find more resources to learn about Angular CDK(and scrolling) at the end of this article.<\/p>\n<h2>Implementation<\/h2>\n<h3>The Source of Data<\/h3>\n<p>For this project, we\u2019ll use the <a href=\"https:\/\/randomuser.me\/\">Random User Generator<\/a> API, which is free and allows us to generate random user data.<\/p>\n<p>Just open a new tab in your favorite browser to see an example: <a href=\"https:\/\/randomuser.me\/api\/\">https:\/\/randomuser.me\/api\/<\/a>. The result will come as a <em>User<\/em> object with several properties.<\/p>\n<p>Now try the following URL to get the pictures only of <code>100<\/code> users: <a href=\"https:\/\/randomuser.me\/api\/?results=100&amp;inc=picture\">https:\/\/randomuser.me\/api\/?results=100&amp;inc=picture<\/a>. You\u2019ll get a result as follows:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json shcb-wrap-lines\">{\n  <span class=\"hljs-attr\">\"results\"<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">\"picture\"<\/span>: {\n        <span class=\"hljs-attr\">\"large\"<\/span>: <span class=\"hljs-string\">\"https:\/\/randomuser.me\/api\/portraits\/women\/96.jpg\"<\/span>,\n        <span class=\"hljs-attr\">\"medium\"<\/span>: <span class=\"hljs-string\">\"https:\/\/randomuser.me\/api\/portraits\/med\/women\/96.jpg\"<\/span>,\n        <span class=\"hljs-attr\">\"thumbnail\"<\/span>: <span class=\"hljs-string\">\"https:\/\/randomuser.me\/api\/portraits\/thumb\/women\/96.jpg\"<\/span>\n      }\n    },\n    {\n      <span class=\"hljs-attr\">\"picture\"<\/span>: {\n        <span class=\"hljs-attr\">\"large\"<\/span>: <span class=\"hljs-string\">\"https:\/\/randomuser.me\/api\/portraits\/women\/77.jpg\"<\/span>,\n        <span class=\"hljs-attr\">\"medium\"<\/span>: <span class=\"hljs-string\">\"https:\/\/randomuser.me\/api\/portraits\/med\/women\/77.jpg\"<\/span>,\n        <span class=\"hljs-attr\">\"thumbnail\"<\/span>: <span class=\"hljs-string\">\"https:\/\/randomuser.me\/api\/portraits\/thumb\/women\/77.jpg\"<\/span>\n      }\n    }\n  ]\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019re ready to go! Let\u2019s create the project as a next step.<\/p>\n<h3>The Initial Project<\/h3>\n<p>Let\u2019s create a brand new Angular project using the Angular CLI tool.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">ng <span class=\"hljs-keyword\">new<\/span> load-images-virtual-scroll --routing --style css --prefix demo\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next, let\u2019s add the Angular Material dependency, which will install the <code>Angular CDK<\/code> library under the hood.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">ng<\/span> <span class=\"hljs-selector-tag\">add<\/span> <span class=\"hljs-keyword\">@angular<\/span>\/material\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then, let\u2019s create a couple of modules and components to have a good code architecture.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">ng generate <span class=\"hljs-built_in\">module<\/span> shared --<span class=\"hljs-built_in\">module<\/span> app\nng generate <span class=\"hljs-built_in\">module<\/span> shared\/material --<span class=\"hljs-built_in\">module<\/span> shared\nng generate <span class=\"hljs-built_in\">module<\/span> gallery --<span class=\"hljs-built_in\">module<\/span> app\nng generate component gallery --<span class=\"hljs-built_in\">module<\/span> app\nng generate component gallery\/gallery\nng generate service gallery\/gallery \n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Pay attention to every output of those commands to understand what\u2019s happening with your project and directories. We\u2019ll have the following structure:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">|- src\/\n    |- app\/\n        |- app.module.ts\n        |- app.component.ts|html|css\n        |- gallery\/\n            |- gallery.module.ts\n            |- gallery.component.ts|html|css\n            |- gallery.service.ts\n        |- shared\/\n            |- shared.module.ts\n            |- material\/\n                |- material.module.ts\n<\/code><\/span><\/pre>\n<h3>The Data Model<\/h3>\n<p>It\u2019s time to define the data model. Create a new file <code>app\/gallery\/user.ts<\/code><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">export<\/span> <span class=\"hljs-selector-tag\">interface<\/span> <span class=\"hljs-selector-tag\">User<\/span> {\n  <span class=\"hljs-attribute\">picture<\/span>: {\n    large: string;\n    <span class=\"hljs-attribute\">medium<\/span>: string;\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Using Angular Material Components<\/h3>\n<p>Before start using the Angular Material components, let\u2019s add their modules in the <code>material.module.ts<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ material.module.ts<\/span>\n<span class=\"hljs-comment\">\/\/ ... other imports<\/span>\n<span class=\"hljs-keyword\">import<\/span> { MatToolbarModule } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@angular\/material\/toolbar'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { MatGridListModule } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@angular\/material\/grid-list'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { ScrollingModule, CdkScrollableModule} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@angular\/cdk\/scrolling'<\/span>;\n\n@NgModule({\n  <span class=\"hljs-attr\">declarations<\/span>: &#91;],\n  <span class=\"hljs-attr\">imports<\/span>: &#91;\n    CommonModule,\n    MatToolbarModule,\n    MatGridListModule,\n    ScrollingModule,\n    CdkScrollableModule\n  ],\n  <span class=\"hljs-attr\">exports<\/span>: &#91;\n    MatToolbarModule,\n    MatGridListModule,\n    ScrollingModule,\n    CdkScrollableModule\n  ]\n})\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MaterialModule<\/span> <\/span>{ }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This module is intended to define all material and CDK modules. That\u2019s why we added the <code>ScrollingModule<\/code> and <code>CdkScrollableModule<\/code> from the <code>@angular\/cdk\/scrolling<\/code> package. This is important before start using the Virtual Scrolling implementation.<\/p>\n<p>Next, update the <code>shared.module.ts<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ shared.module.ts<\/span>\n\n<span class=\"hljs-comment\">\/\/ ...other imports<\/span>\n<span class=\"hljs-keyword\">import<\/span> { MaterialModule } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/material\/material.module'<\/span>;\n\n\n@NgModule({\n  <span class=\"hljs-attr\">declarations<\/span>: &#91;],\n  <span class=\"hljs-attr\">imports<\/span>: &#91;\n    CommonModule,\n    MaterialModule\n  ],\n  <span class=\"hljs-attr\">exports<\/span>: &#91;\n    MaterialModule\n  ]\n})\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">SharedModule<\/span> <\/span>{ }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Creating the Angular Service<\/h3>\n<p>Update the content of the <code>gallery.service.ts<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { HttpClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@angular\/common\/http'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Injectable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@angular\/core'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Observable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'rxjs'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { User } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/user'<\/span>;\n\n\n@Injectable({\n  <span class=\"hljs-attr\">providedIn<\/span>: <span class=\"hljs-string\">'root'<\/span>\n})\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">GalleryService<\/span> <\/span>{\n\n  <span class=\"hljs-keyword\">constructor<\/span>(private httpClient: HttpClient) { }\n\n  getImages(count: number = <span class=\"hljs-number\">100<\/span>): Observable&lt;{<span class=\"hljs-attr\">results<\/span>: User&#91;]}&gt; {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">this<\/span>.httpClient.get&lt;{<span class=\"hljs-attr\">results<\/span>: User&#91;]}&gt;(<span class=\"hljs-string\">`https:\/\/randomuser.me\/api\/?results=<span class=\"hljs-subst\">${count}<\/span>&amp;inc=picture`<\/span>);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This Angular Service defines a single method <code>getImages<\/code> to retrieve a list of objects containing the required images for the gallery.<\/p>\n<h3>Creating the Gallery Component<\/h3>\n<p>Let\u2019s update the <code>gallery.component.ts<\/code> file with the following content.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ gallery.component.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Component, OnInit } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@angular\/core'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Observable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'rxjs'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { GalleryService } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/gallery.service'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { User } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/user'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { map } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'rxjs\/operators'<\/span>;\n\n@Component({\n <span class=\"hljs-comment\">\/\/ ...<\/span>\n})\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">GalleryComponent<\/span> <span class=\"hljs-title\">implements<\/span> <span class=\"hljs-title\">OnInit<\/span> <\/span>{\n  <span class=\"hljs-attr\">users$<\/span>: Observable&lt;User&#91;]&gt;;\n\n  <span class=\"hljs-keyword\">constructor<\/span>(private galleryService: GalleryService) {}\n\n  ngOnInit(): <span class=\"hljs-keyword\">void<\/span> {\n    <span class=\"hljs-keyword\">this<\/span>.users$ = <span class=\"hljs-keyword\">this<\/span>.galleryService\n      .getImages(<span class=\"hljs-number\">120<\/span>)\n      .pipe(map(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ results }<\/span>) =&gt;<\/span> results));\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>So what\u2019s happening here?<\/p>\n<ul>\n<li>It creates an <em>Observable<\/em> attribute <code>users$<\/code> to process the data as a <strong>stream<\/strong>.<\/li>\n<li>It injects a <code>GalleryService<\/code> instance using the DI framework(Dependency Injection)<\/li>\n<li>Once the component is being initialized(<em>ngOnInit<\/em>) it will perform an HTTP call through the service.<\/li>\n<li>Since the app is interested in the images only, we can use the <code>.pipe()<\/code> function to use a <code>map<\/code> operator to \u201cextract\u201d the <code>results<\/code> attribute only(See the example result from the API above).<\/li>\n<\/ul>\n<p>We already got the data at this point. The next step involves the creation of the <strong>View Port<\/strong> to start using the Virtual Scrolling solution.<\/p>\n<p>Add the following code in the <code>gallery.component.html<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">cdk-virtual-scroll-viewport<\/span> <span class=\"hljs-attr\">itemSize<\/span>=<span class=\"hljs-string\">\"42\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"viewport\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">mat-grid-list<\/span> <span class=\"hljs-attr\">cols<\/span>=<span class=\"hljs-string\">\"4\"<\/span> <span class=\"hljs-attr\">rowHeight<\/span>=<span class=\"hljs-string\">\"1:1\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">mat-grid-tile<\/span> *<span class=\"hljs-attr\">cdkVirtualFor<\/span>=<span class=\"hljs-string\">\"let user of users$ | async\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> &#91;<span class=\"hljs-attr\">src<\/span>]=<span class=\"hljs-string\">\"user.picture.large\"<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">mat-grid-tile<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">mat-grid-list<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">cdk-virtual-scroll-viewport<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Let\u2019s explain the previous code snippet.<\/p>\n<ul>\n<li>The <code>&lt;cdk-virtual-scroll-viewport&gt;<\/code> element defines the <strong>View Port<\/strong> area to be able to render the items that fit in it.<\/li>\n<li>The <code>itemSize<\/code> directive tells the <code>cdk-virtual-scroll-viewport<\/code> the size of the items in the list(in pixels).<\/li>\n<li>The <code>class=&quot;viewport&quot;<\/code> is important here since, by default, the size of the View Port is 0, which means you\u2019ll see a blank page only if the dimension is not set.<\/li>\n<li>The <code>*cdkVirtualFor<\/code> is required here instead of using <code>*ngFor<\/code>(from Angular). This directive has the same API as <code>*ngFor<\/code>.<\/li>\n<li>The <code>&lt;mat-grid-list&gt;<\/code> defines the layout for the gallery and the<code>&lt;mat-grid-tile&gt;<\/code> element will contain every image. You can find more details about the <a href=\"https:\/\/material.angular.io\/components\/grid-list\/overview\">Grid list<\/a> component in the official documentation.<\/li>\n<\/ul>\n<p><strong>Important!<\/strong> Do not forget to edit the <code>gallery.component.css<\/code> file to set the View Port size(<code>width x height<\/code>).<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">mat-grid-tile<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: lightblue;\n}\n\n<span class=\"hljs-selector-class\">.viewport<\/span> {\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">500px<\/span>;\n  <span class=\"hljs-attribute\">max-width<\/span>: <span class=\"hljs-number\">700px<\/span>;\n  <span class=\"hljs-attribute\">margin-left<\/span>: auto;\n  <span class=\"hljs-attribute\">margin-right<\/span>: auto;\n  <span class=\"hljs-attribute\">border<\/span>: <span class=\"hljs-number\">1px<\/span> solid <span class=\"hljs-number\">#e9e7e9<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>As a final step, you can open your browser\u2019s developer tools to inspect the DOM\nand verify that elements are dynamically being added and removed as the next screenshot shows.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1614709800\/e-60354e32ba3535006813ce00\/itetkdc2edvq57yvjann.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1298\" height=\"595\"\/><\/p>\n<h2>Live Demo<\/h2>\n<p>This project is available on <a href=\"https:\/\/github.com\/luixaviles\/image-gallery-virtual-scroll\">GitHub<\/a> and <a href=\"https:\/\/codesandbox.io\/s\/load-images-virtual-scroll-angular-z22xg\">CodeSandbox<\/a>.<\/p>\n<p>If you prefer, you can play around with the project here too:<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/load-images-virtual-scroll-angular-z22xg?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"Load Images Virtual Scroll Angular\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Resources<\/h2>\n<ul>\n<li>\n<a href=\"https:\/\/material.angular.io\/cdk\/categories\">Angular CDK<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/material.angular.io\/cdk\/scrolling\/overview\">Scrolling<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/github.com\/WICG\/virtual-scroller\/tree\/master\/study-group\">Virtual scroller study group<\/a>\n<\/li>\n<\/ul>\n<p>Feel free to reach out on <a href=\"https:\/\/twitter.com\/luixaviles\">Twitter<\/a> if you have any questions. Follow me on <a href=\"https:\/\/github.com\/luixaviles\">GitHub<\/a> to see more about my work.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[391,134,374,371],"class_list":["post-28019","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-angular","tag-guest-post","tag-performance","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>How to use virtual scrolling to load images in Angular<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to use virtual scrolling to load images in Angular\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2021-04-07T20:12:36+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-02-16T20:57:11+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"How to use virtual scrolling to load images in Angular\",\"datePublished\":\"2021-04-07T20:12:36+00:00\",\"dateModified\":\"2025-02-16T20:57:11+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/\"},\"wordCount\":10,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"keywords\":[\"Angular\",\"Guest Post\",\"Performance\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2021\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/\",\"name\":\"How to use virtual scrolling to load images in Angular\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"datePublished\":\"2021-04-07T20:12:36+00:00\",\"dateModified\":\"2025-02-16T20:57:11+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to use virtual scrolling to load images in Angular\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"How to use virtual scrolling to load images in Angular","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/","og_locale":"en_US","og_type":"article","og_title":"How to use virtual scrolling to load images in Angular","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/","og_site_name":"Cloudinary Blog","article_published_time":"2021-04-07T20:12:36+00:00","article_modified_time":"2025-02-16T20:57:11+00:00","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/"},"author":{"name":"","@id":""},"headline":"How to use virtual scrolling to load images in Angular","datePublished":"2021-04-07T20:12:36+00:00","dateModified":"2025-02-16T20:57:11+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/"},"wordCount":10,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"keywords":["Angular","Guest Post","Performance","Under Review"],"inLanguage":"en-US","copyrightYear":"2021","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/","name":"How to use virtual scrolling to load images in Angular","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"datePublished":"2021-04-07T20:12:36+00:00","dateModified":"2025-02-16T20:57:11+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-use-virtual-scrolling-to-load-images-in-angular\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to use virtual scrolling to load images in Angular"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28019","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28019"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28019\/revisions"}],"predecessor-version":[{"id":36862,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28019\/revisions\/36862"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28019"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28019"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28019"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}