Lazy Loading Images Angular - Increase Performance Without Libraries
Lazy Loading Images Angular - Increase Performance Without Libraries

In this post you will learn how to implement lazy loading of images inside Angular.

Do we have a problem?

Just imagine that we have a website and we typically throw lots of image tags inside our markup to load images. This is completely fine with your internet at home which is blazingly fast. But it is not fine for some slow mobile network. Obviously we want to optimize our images to make sure that they are being loaded in the best possible way and work even with slow internet.

big image

Here for our project I prepared several big images which are really heavy to load. This is one of them.

small iamge

I also prepared the same image but made them only 20px size. It looks like an extremely blurry version of original image. We will render these small images which can be loaded fast first and only after our big image is being loaded we will show it instead.

Some of the big images are 5mb each but 20px smaller alternative is only 500 bytes.

Small images

The next question is how to generate such small images. First way is to use any online project which allows you to compress images. Another possible variant is to use ffmpeg tool to generate it.

ffmpeg - i src/assets/image-5.jpeg -vf scale=20:-1 src/assets/image-5-small.jpeg

Here we are scaling our original image to 20px and save it with another name.

The next step that I want to do is to implement an additional Angular component which will implement for us progressive image loading.

// src/app/progressiveImage/progressiveImage.component.html
{{imageUrlSmall}}
{{imageUrl}}

Here we render both inputs that we want to provide to the component.

@Component({
  selector: 'progressive-image',
  templateUrl: './progressiveImage.component.html',
  styleUrls: ['./progressiveImage.component.css'],
  standalone: true,
  imports: [CommonModule],
})
export class ProgressiveImageComponent {
  @Input({ required: true }) imageUrl!: string;
  @Input({ required: true }) imageUrlSmall!: string;
}

We created a progressive-image component where we will provide a url to big and small image.

// src/app/app.component.html
<div class="image-container">
  <div class="image-item">
    <progressive-image
      imageUrl="/assets/image-1.jpeg"
      imageUrlSmall="/assets/image-1-small.jpeg"
    ></progressive-image>
  </div>
  <div class="image-item">
    <progressive-image
      imageUrl="/assets/image-2.jpeg"
      imageUrlSmall="/assets/image-2-small.jpeg"
    ></progressive-image>
  </div>
  <div class="image-item">
    <progressive-image
      imageUrl="/assets/image-3.jpeg"
      imageUrlSmall="/assets/image-3-small.jpeg"
    ></progressive-image>
  </div>
  <div class="image-item">
    <progressive-image
      imageUrl="/assets/image-4.jpeg"
      imageUrlSmall="/assets/image-4-small.jpeg"
    ></progressive-image>
  </div>
  <div class="image-item">
    <progressive-image
      imageUrl="/assets/image-5.jpeg"
      imageUrlSmall="/assets/image-5-small.jpeg"
    ></progressive-image>
  </div>
</div>

Here I rendered progressive-image component for every image that we have.

simple comp

As you can see we successfully renders all urls in our component.

Now lets try to render images

// src/app/progressiveImage/progressiveImage.component.html
<div>
  <img [src]="imageUrl" class="image"/>
</div>

This is how we typically render images without any optimization. Additionally as you can see we didn't try to specify any size because we want to make the component as flexible as possible.

// src/app/progressiveImage/progressiveImage.component.css
.image {
  display: block;
  width: 100%;
}

This is why we set our image width to 100%. Now let's style it from the outside.

// src/app/app.component.css
.image-container {
  width: 500px;
  margin: 0 auto;
}

.image-item {
  margin-bottom: 20px;
}

Here we provided a fixed width and margins for our elements.

comp with images

Now our images look amazing like on Instagram for example.

Improving performance

Let's start with improving our performance. The easiest step is to load all our images with lazy attribute. It means that browser will load these images when it has resources for that. So first of all we are rendering the whole page and only then our images.

// src/app/progressiveImage/progressiveImage.component.html
<div>
  <img [src]="imageUrl" class="image" loading="lazy"/>
</div>

You can safely use loading="lazy" in all modern browsers.

3g

In order to test the speed of the loading we must lower the speed of the internet. Here I selected Slow 3G which shows exactly how our website work on the slow internet. As you can see with 3G all these images were loading long time. All our big images take more that 20 seconds to load.

Lazy is not a good solution for a slow internet

Rendering small images

This is why our next step is to render small images before our big images are ready.

<div
  [ngStyle]="{ 'background-image': 'url(' + imageUrlSmall + ')' }"
  [ngClass]="{ placeholder: true, 'placeholder-loaded': isLoaded }"
>
  <img
    [src]="imageUrl"
    [ngClass]="{ image: true, 'image-loaded': isLoaded }"
    loading="lazy"
    (load)="onImageLoad()"
  />
</div>

Here we render a small image on the div and it covers fully our image.

export class ProgressiveImageComponent {
  ...
  isLoaded = false;

  onImageLoad() {
    this.isLoaded = true;
  }
}

We also added onImageLoad which is called when our big images is fully loaded. It sets isLoading property to false so we render a big image instead of small one.

.image {
  display: block;
  width: 100%;
  opacity: 0;
}

.placeholder {
  background-repeat: no-repeat;
  background-size: cover;
  filter: blur(10px);
}

.image-loaded {
  opacity: 1;
}

.placeholder-loaded {
  filter: none;
}

We hided our big image with opacity: 0 until it is fully loaded. To our small placeholder image we added a blur filter to make it more realistic. When image is fully loaded we make it visible again.

blurred

This is how our blurred small image looks like. User can understand how finished image looks like and it loads blazingly fast.

fnished

Here is how it look when some of our images are already loaded and some not.

And actually if you want to learn Angular with NgRx from empty folder to a fully functional production application make sure to check my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done