|
| 1 | +--- |
| 2 | +title: "NgOptimizedImage Directive in Angular 15" |
| 3 | +excerpt: "Image optimisation techniques and performance improvements with the new NgOptimizedImage directive in Angular 15" |
| 4 | +tags: Web NgOptimizedImage Angular |
| 5 | +authors: |
| 6 | +- Manisha Arya |
| 7 | +header: |
| 8 | + teaser: /assets/images/post/ng-optimized-image-directive-in-angular-banner.png |
| 9 | + teaser_alt: NgOptimizedImage Directive in Angular 15 |
| 10 | +category: Frontend |
| 11 | +--- |
| 12 | + |
| 13 | + |
| 14 | + |
| 15 | +# Introduction |
| 16 | + |
| 17 | +Images constitute a major part of any web application. They heavily impact the page loading times and performance of the web page. |
| 18 | + |
| 19 | +They are one of the main contributors to the [**Core Web Vitals**](https://web.dev/vitals/#core-web-vitals"), identified by Google, to measure website performance. |
| 20 | + |
| 21 | +The metrics that make up the core web vitals are as follows: |
| 22 | + |
| 23 | +{% include |
| 24 | + components/figure.html |
| 25 | + url="/assets/images/post/ng-optimized-image-directive-in-angular-15-0.png" |
| 26 | + description="Core Web Vitals" |
| 27 | +%} |
| 28 | + |
| 29 | +To improve the Web Vitals metrics, loading times, and responsiveness of a single-page-application, Angular has introduced the [NgOptimizedImage](https://angular.io/api/common/NgOptimizedImage) directive with Angular v15. This new directive improves image loading performance by providing image optimisation techniques and also enforcing best practices. |
| 30 | + |
| 31 | +# Default `<img>`Tag |
| 32 | + |
| 33 | +Before diving into the key features of the `NgOptimizedImage` directive, let's first examine how the default `<img>` tag functions and the extra optimisation techniques that can be implemented with it. |
| 34 | + |
| 35 | +**No default lazy loading:** the following example includes 6 `img` tags which the browser requests and eagerly loads all of them. |
| 36 | + |
| 37 | +{% include |
| 38 | +components/figure.html |
| 39 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-1.png" |
| 40 | +description=" List of images in HTML" |
| 41 | +%} |
| 42 | + |
| 43 | +This can be costly on low-bandwidth devices when there are pages with huge number of images. |
| 44 | + |
| 45 | +{% include |
| 46 | +components/figure.html |
| 47 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-2.png" |
| 48 | +description="All images eagerly loaded with img directive" |
| 49 | +%} |
| 50 | + |
| 51 | +This problem can be mitigated by using the `loading="lazy"` attribute. It defers the loading of images until they are needed. |
| 52 | + |
| 53 | +Prioritising the loading of a critical image can also be done using the `fetchPriority` attribute. For example, by using `fetchpriority="low"` for images in a carousel. |
| 54 | + |
| 55 | +{% include |
| 56 | +components/figure.html |
| 57 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-3.png" |
| 58 | +description="Image optimisation with img tag" |
| 59 | +%} |
| 60 | + |
| 61 | +# `NgOptimizedImage` directive |
| 62 | + |
| 63 | +This directive includes built-in image optimisation techniques and improves the website’s performance with minimal configuration. |
| 64 | + |
| 65 | +## Integrating `NgOptimizedImage` |
| 66 | + |
| 67 | +To integrate the `NgOptimizedImage` directive, follow these steps: |
| 68 | +1. Import `NgOptimizedImage` into your standalone component or module. |
| 69 | +2. Replace the `src` attribute of the image with `ngSrc`. |
| 70 | +3. Specify the `width` and `height` attributes that must be specified for the `NgOptimizedImage` directive in one of the following ways. |
| 71 | + |
| 72 | +``` |
| 73 | +//import in module |
| 74 | +import { NgModule } from '@angular/core'; |
| 75 | +import { CommonModule, NgOptimizedImage } from '@angular/common'; |
| 76 | +@NgModule({ imports: [CommonModule, NgOptimizedImage], }) |
| 77 | +
|
| 78 | +// use in component |
| 79 | +import { Component } from '@angular/core'; |
| 80 | +@Component({ |
| 81 | + selector: 'app-optimised-image-catalog', |
| 82 | + template: `<img ngSrc="blog-food-img" width="500" height="300" alt="Food Blog Image"/> `, |
| 83 | +}) |
| 84 | +``` |
| 85 | + |
| 86 | +Alternatively, you can add it directly in a standalone component. |
| 87 | + |
| 88 | +``` |
| 89 | +import { Component } from '@angular/core'; |
| 90 | +import { CommonModule, NgOptimizedImage } from '@angular/common'; |
| 91 | +@Component({ |
| 92 | + selector: 'app-optimised-image-catalog', standalone: true, |
| 93 | + imports: [ CommonModule, NgOptimizedImage ], |
| 94 | + template: ` <img ngSrc="blog-food-img" width="500" height="300" alt="Food Blog Image" /> `, }) |
| 95 | +``` |
| 96 | + |
| 97 | +## Key features of `NgOptimizedImage` |
| 98 | + |
| 99 | +The following are the main highlights of this directive: |
| 100 | + |
| 101 | +### Intelligent lazy loading |
| 102 | + |
| 103 | +By default, the directive lazy loads non-critical images and only eagerly loads images marked with the `priority` attribute. This ensures that most images are loaded optimally. |
| 104 | + |
| 105 | +{% include |
| 106 | +components/figure.html |
| 107 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-4.png" |
| 108 | +description="Six img tags in HTML" |
| 109 | +%} |
| 110 | + |
| 111 | +The following shows that the browser only requests 4 images that are on the view port with the `NgOptimizedImage` directive. |
| 112 | + |
| 113 | +{% include |
| 114 | +components/figure.html |
| 115 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-5.png" |
| 116 | +description="Images loaded lazily" |
| 117 | +%} |
| 118 | + |
| 119 | +### Serve responsive images |
| 120 | + |
| 121 | +Before implementing responsive images using the directive, you must consider how the `ngSrcset` and `sizes` attributes work. |
| 122 | + |
| 123 | +``` |
| 124 | +<img ngSrc="business.png" ngSrcset="100w, 200w, 300w" priority sizes="50vw"> |
| 125 | +``` |
| 126 | + |
| 127 | +* `ngSrcset` specifies three different image sources with widths of 100w, 200w, and 300w. The directive supports both width descriptors for example, `100w`, and [density descriptors](https://web.dev/codelab-density-descriptors/#use-density-descriptors-to-serve-multiple-images), for example,`1x`. |
| 128 | + |
| 129 | +* `sizes` specifies the width of the image container or layout in CSS units, with different sizes for different screen widths. |
| 130 | + |
| 131 | + |
| 132 | +#### srcset |
| 133 | + |
| 134 | +* The `srcset` attribute can be manually defined by providing your own `ngSrcset` attribute, as given in the example above. |
| 135 | + |
| 136 | +* For responsive images, to automatically generate the `srcset` attribute, you only have to define the sizes attribute. For example, if your image takes 50% of the viewport, set the the size to 50vw and the browser selects the image in the `srcset` that is closest to 50% of the viewport width. |
| 137 | + |
| 138 | +* If you have varying image widths for different sizes of screens you can use media queries. For example in a grid layout, you want image to be 100 percent of screen on devices under 768px wide, else it should be 50%. You can achieve this in the following way: |
| 139 | + |
| 140 | +``` |
| 141 | +<img ngSrc="business.png" width="400" height="200" priority sizes="(max-width: 768px) 100vw, 50vw"> |
| 142 | +``` |
| 143 | + |
| 144 | +### Image loader |
| 145 | + |
| 146 | +On a web page, most images are served without regard for the size of the image container. This means that even if you only need a 200 pixel x 100 pixel image, a 2000 pixel x 1000 pixel image is downloaded. |
| 147 | + |
| 148 | +This can be solved by providing an image loader function. This is a function that modifies the provided `src`, and generates multiple URLs to request the image in different sizes. These multiple URLs are used in the automatic [srcset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset") generation, so that the images are served with respect to the viewport size. |
| 149 | + |
| 150 | +{% include |
| 151 | +components/figure.html |
| 152 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-6.png" |
| 153 | +description="NgOptimizedImage automatically sets srcset attribute based on width attribute" |
| 154 | +%} |
| 155 | + |
| 156 | +#### `NgOptimizedImage` loader API |
| 157 | + |
| 158 | +The Angular directive provides a built in loader API for third-party image services such as Imagekit, Cloudfare, Imgix. For more information see [the Angular documentation](https://angular.io/guide/image-directive#configuring-an-image-loader-for-ngoptimizedimage). |
| 159 | + |
| 160 | +In the following example, the directive creates two image URLs in `srcset` for the different widths and density of an image for an image of size 50\*50. |
| 161 | + |
| 162 | +``` |
| 163 | + // in module providers add |
| 164 | +import { provideImageKitLoader } from '@angular/common'; |
| 165 | +providers: provideImageKitLoader('https://ik.imagekit.io/Your-ID') |
| 166 | +
|
| 167 | +// in markup img tag |
| 168 | +<img priority ngSrc="custom-img.webp" width="50" height="50" alt="custom image"/> |
| 169 | +
|
| 170 | +//in DOM img tag is changed to this |
| 171 | +<img _ngcontent-stl-c204="" priority="" ngsrc="custom-img.webp" width="50" height="50" |
| 172 | +alt="custom image" ng-reflect-priority="" |
| 173 | +ng-reflect-ng-src="custom-img.webp" ng-reflect-width="50" ng-reflect-height="50" |
| 174 | +loading="eager" fetchpriority="high" ng-img="true" |
| 175 | +src="https://ik.imagekit.io/ith29bzjr/tr:q-auto/custom-img.webp" |
| 176 | +srcset="https://ik.imagekit.io/ith29bzjr/tr:q-auto,w-50/custom-img.webp 1x, https://ik.imagekit.io/ith29bzjr/tr:q-auto,w-100/custom-img.webp 2x"> |
| 177 | + ``` |
| 178 | + |
| 179 | +#### Custom loaders |
| 180 | + |
| 181 | +If your image service is not provided by the `NgOptimizedImage` default loaders, you can create your custom loader as shown in the following example. |
| 182 | + |
| 183 | +> **_NOTE:_** You must include a width check for creating the URL otherwise the provider generates the src with an undefined width. |
| 184 | +
|
| 185 | +``` |
| 186 | +// in module add to providers |
| 187 | +import { NgOptimizedImage, IMAGE_LOADER, ImageLoaderConfig } from '@angular/common'; |
| 188 | +[{ provide: IMAGE_LOADER, |
| 189 | + useValue: (config: ImageLoaderConfig) => { |
| 190 | + const url = config.src && config.width ? `./assets/content/${config.src}-img-${config.width}.webp` : `./assets/content/${config.src}-img.webp`; |
| 191 | + return url; } }], |
| 192 | + |
| 193 | +// in markup |
| 194 | + <img priority ngSrc="custom-img" width="50" height="50" alt="custom image"/> |
| 195 | + |
| 196 | +// in DOM the img tag is changed to this |
| 197 | + <img _ngcontent-btb-c158="" ng-reflect-ng-src="business" ng-reflect-width="400" ng-reflect-height="400" |
| 198 | + alt="business" width="400" height="400" loading="lazy" fetchpriority="auto" |
| 199 | + ng-img="true" src="./assets/content/business-img.webp" |
| 200 | + srcset="./assets/content/business-img-400.webp 1x, ./assets/content/business-img-800.webp 2x"> |
| 201 | + ``` |
| 202 | + |
| 203 | +### preconnect image URL |
| 204 | + |
| 205 | +`NgOptimizedImage` throws a warning in browser console if there is no `preconnect` tag for the third-party image URLs in the head of the page in `index.html`. For more information on preconnect, please refer [here](https://web.dev/preconnect-and-dns-prefetch/). |
| 206 | + |
| 207 | +`<link rel="preconnect" href="https://my.cdn.origin" />` |
| 208 | + |
| 209 | +{% include |
| 210 | +components/figure.html |
| 211 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-7.png" |
| 212 | +description="Browser Warning without preconnect image url" |
| 213 | +%} |
| 214 | + |
| 215 | +## Performance results |
| 216 | + |
| 217 | +In the following example, an [Angular App](https://github.com/manisha-backbase/image-optimisation-angular) was created with two pages: one using the Angular `NgOptimizedImage` directive, and one with the native image tag. The app was [Deployed](https://image-optimisation-angular.vercel.app/ngoptimized-img-list) and the results compared using [PageSpeed](https://pagespeed.web.dev/). |
| 218 | + |
| 219 | +{% include |
| 220 | +components/figure.html |
| 221 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-8.png" |
| 222 | +description="Results with native image tag" |
| 223 | +%} |
| 224 | + |
| 225 | +{% include |
| 226 | +components/figure.html |
| 227 | +url="/assets/images/post/ng-optimized-image-directive-in-angular-15-9.png" |
| 228 | +description="Results with NgOptimizedImage directive" |
| 229 | +%} |
| 230 | + |
| 231 | +## In conclusion `NgOptimizedImage` means significant improvements... |
| 232 | +The PageSpeed results for the application using `NgOptimizedImage` show a significant reduction in time for the largest contentful paint. The intelligent lazy loading feature also improves the cumulative layout shift. In conclusion, the `NgOptimizedImage` directive drastically improves the performance of your application by enforcing the best practices for image optimisation. |
0 commit comments