How to Optimize Images in Angular Using ngOptimizedImage Directive
Tags: angular, frontend, javascript, tutorial
Published: 2024-05-18 07:41:15

As developers, we create different types of applications. Sometimes, we work on data processing with forms or data visualization with grids or charts. But sometimes, we build applications that focus mainly on images. While making an app to show images might seem simple, do you know how to make sure it performs well?

When building an app focused on images, we need to learn about lazy loading, prioritization, and best practices like using CDNs and following web vitals such as [**layout shifts**](https://web.dev/articles/optimize-cls#images_without_dimensions) or addressing cumulative layout shift ([**CLS**](https://web.dev/articles/cls)). This includes setting correct dimensions (width or height) and avoiding errors like using percentage dimensions in your CSS.

Yes, it seems like there's a lot to handle, but these points are crucial for building a good image-focused application. However, I have good news: Angular offers the [ngOptimizedImage](https://angular.io/api/common/NgOptimizedImage) directive to make things easier. It helps you follow performance best practices without much effort, so you can focus on delivering your application while maintaining top performance.

The ngOptimizedImage directive is indeed a game changer in the way we build high-performance applications with Angular. Today, we'll see it in action with a real scenario. Let's get started.

## **Scenario**

You have been hired to build an MVP focusing on Special Products. The page must display a list of products with brief descriptions on the homepage.

Here's the plan:

* Create the application.
    
* Retrieve the data using a service.
    
* Develop a component to showcase each product along with its image.
    

At first glance, these tasks seem straightforward. Let's get started and build this project!

## **Set Up the Project**

In your terminal, run the following command to create a new Angular project named `products-offers`:

```bash
ng new products-offers
? Which stylesheet format would you like to use? SCSS   [
https://sass-lang.com/documentation/syntax#scss                ]
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation
(SSG/Prerendering)? No
​
cd new products-offers
```

Go to the `products-offers` folder, edit the app.component.html, and remove the default HTML to be ready for our next step.

Now that our Angular project is ready, the next step is to get the data from the service!

## **Get the Data**

Before creating the service to fetch product data via HTTP request, we need to configure the `HttpClient` in the `app.config` file.

```typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
​
import { routes } from './app.routes';
import {provideHttpClient} from "@angular/common/http";
​
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient()
  ]
};
```

Next, create the service using the Angular CLI with the command `ng g s services/products`.

```bash
C:\Users\DPAREDES\Desktop\products-offers>ng g s services/products   
CREATE src/app/services/products.service.spec.ts (383 bytes)
CREATE src/app/services/products.service.ts (146 bytes)
```

Open the `products.service.ts` file. First, inject the `HttpClient` using the `inject` function, and declare the `API` variable with the endpoint. This will help us make the request to the API.

```typescript
import {inject, Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
​
@Injectable({
  providedIn: 'root'
})
export class ProductsService {
  private API = 'https://fakestoreapi.com/products'
  http = inject(HttpClient)
}
```

To provide better typing, before the `@Injectable`, declare a product type to return a strong type response with `httpClient`.

```typescript
import {inject, Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
​
export type Product = {
  description: string;
  image: string
}
​
@Injectable({
  providedIn: 'root'
})
export class ProductsService {
  private API = 'https://fakestoreapi.com/products'
  http = inject(HttpClient)
  }
```

The final step is to expose the `$products` data using the HttpClient and transform it into signals using the `toSignal()` function.

The final code looks like this:

```typescript
import {inject, Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {toSignal} from "@angular/core/rxjs-interop";
​
export type Product = {
  description: string;
  image: string
}
​
@Injectable({
  providedIn: 'root'
})
export class ProductsService {
  private API = 'https://fakestoreapi.com/products'
  http = inject(HttpClient)
​
  $products = toSignal(this.http.get<Array<Product>>(this.API));
}
```

Perfect, the service is ready. Let's display the products in the AppComponent.

## **Display the Products**

Displaying the products is straightforward. Simply inject the `productService` and utilize the signals `$products` to seamlessly integrate them into the interface.

```typescript
import {Component, inject} from '@angular/core';
import {ProductsService} from "./services/products.service";
​
@Component({
  selector: 'app-root',
  standalone: true,
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
​
  $products = inject(ProductsService).$products;
​
}
```

In the HTML template, utilize the `@for` directive to iterate through the products and include an `<img>` tag along with the product description.

```xml
@for (product of $products(); track product) {
  <img [src]="product.image">
  <p>{{product.description}}</p>
}
```

Save changes. Instead of using `ng serve`, let's build the application and serve it using another HTTP server to get a more realistic view of how the app will work.

Run the command `npm run build`.


![1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ciglr4uzz1skv27azjw7.png)

Navigate to `products-offers\dist\products-offers\browser` and run the command `npx local-web-server` to start a local web server.

```bash
C:\Users\DPAREDES\Desktop\products-offers\dist\products-offers> npx local-web-server     
Need to install the following packages:
  local-web-server@5.3.3
Ok to proceed? (y) y
Listening on http://INNO-D0H:8000, http://192.168.86.21:8000, http://127.0.0.1:8000
```

Go to [**http://127.0.0.1**](http://127.0.0.1/) and tada!! 🥳 The app is working!!!


![2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/el9wnpo8yfgpbq3eqy98.jpeg)

Do you believe the app is finished and ready? Let's measure its performance and ensure it meets our standards.

## **The Performance**

First, navigate again to [**http://127.0.0.1:8000**](http://127.0.0.1:8000/) and open the Chrome Developer Tools. Click on the "Network" tab and reload the page.

😱 Oh no! It seems we're downloading all the images without the user needing to see them! The images amount to 2MB with 29 requests, taking 1.38 seconds. 😖
![3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86spqdjks57hvzixqw0t.png)

We'll continue the party by using the Lighthouse tool to analyze the app and see the results!

![01](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ez18se5rjgd0iv4xjvi.png)

😵 We're only at 54% performance with a 4.8s LCP. 😿 Houston, we have a problem! These are not good numbers for our app. How can we fix this?

Well, I did mention the ngOptimizedImage directive. Let's implement it and see how our app improves.

## **The ngOptimizedImage Directive**

The ngOptimizedImage directive help us to implement best practices for loading images and makes it easy to adopt performance best practices.

It helps to follow the [**Largest Contentful Paint (LCP)**](http://web.dev/lcp) prioritization rule by automatically setting the `fetchpriority` attribute on the `<img>` tag for the most important image. We can then lazy load other images by default using a preconnect link tag in the document head using an automatically generated `srcset` attribute.

Also we can do more stuff like use [**image CDN URLs to apply image optimizations**](https://web.dev/image-cdns/#how-image-cdns-use-urls-to-indicate-optimization-options) and prevent layout shift by requiring `width` and `height` with a warning if `width` or `height` have been set incorrectly.

Now that we understand the benefits of the ngOptimizedImage directive, let's integrate it into our app.

### **Using the ngOptimizedImage Directive**

Open the `app.component.ts` to import the `NgOptimizedImage` from `@angular/common;` in the `imports` section.

```typescript
import {Component, inject} from '@angular/core';
import {ProductsService} from "./services/products.service";
import {NgOptimizedImage} from "@angular/common";
​
@Component({
  selector: 'app-root',
  standalone: true,
  templateUrl: './app.component.html',
  imports: [
    NgOptimizedImage
  ],
  styleUrl: './app.component.scss'
})
export class AppComponent {
​
  $products = inject(ProductsService).$products;
​
}
```

Open the app.component.html file and change the `src` attribute to `ngSrc`. Additionally, implement default `width` and `height` attributes.

> ***Some IDEs like WebStorm may display recommendations to set***`width` and `height`.


![ea](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ox705h7ok84ca809n4cq.png)

Save changes and rebuild the app with the `ng build` command.


![fb](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s7fqg5foish9o0u85zjc.png)

Navigate again to [**http://127.0.0.1**](http://127.0.0.1/) and explore the network requests and Lighthouse results.


![bb](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k2hshw8buy2fe2qhhy66.gif)

Good news!! 😃 We've made significant improvements to the performance:

* Reduced the request to 5/14 request
    
* Added Lazy Loading
    
* Earned 97% performance in Lighthouse
    
* Got LCP to 1.2 seconds
    

The ngOptimizedImage dDirective did a lot of magic for us by setting the loading and fetch priority and handling the automatic lazy loading of images. 🥳🎉

![5c](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nv6uvx8gyda1gmoj5fse.png)

NgOptimizedImage helps us with even more functionalities, this is just a taste! I recommend checking out the official documentation.

> ***Learn more about the image directive at***[***https://angular.io/guide/image-directive***](https://angular.io/guide/image-directive)***.***

## **Recap**

We've taken our first steps in utilizing Chrome DevTools to analyze our app's performance. It's remarkable how effortlessly we can optimize our applications using the NgOptimizedImage directive, requiring only a few lines of code.

* Source code: [**https://github.com/danywalls/using-ngOptimizeImage-angular**](https://github.com/danywalls/using-ngOptimizeImage-angular)
    
* NgOptimizedImage API: [**https://angular.io/api/common/NgOptimizedImage**](https://angular.io/api/common/NgOptimizedImage)

> Photo by Irene Strong on Unsplash