Lazy loading images in your angularJS project using the IntersectionObserver API
Tags: angular, intersectionobserver, javascript
Published: 2017-10-17 16:00:10

---
title: Lazy loading images in your angularJS project using the IntersectionObserver API
published: true
description: A simple method to avoid scroll listeners 
tags: angular, IntersectionObserver, js
---
![alt text][logo]
[logo]: https://media.giphy.com/media/xUOrw2DnHjY0zleq0o/giphy.gif

So for a while I've been trying to reduce the number of scroll listeners in my project that's until I stumbled on the Intersection Observer API after running a lighthouse test on my web app. The implementation was so simple and straightforward that I set out immediately to implement it.

**What is the Intersection Observer**

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

By default, IntersectionObservers calculate how much of a target element overlaps (or “intersects with”) the visible portion of the page, also known as the browser’s “viewport.

**How do I fire this up?**

As I said above, the setup is very simple. All we need to do is define a new intersection observer with a callback function and then observe a target element.

Too much talk let's see!!

```javascript
const observer = new IntersectionObserver(callback);
observer.observe(element)
```
It's that simple, we can load more new items on the DOM, load an image asynchronously and so much more once the target element comes into view. Let's see what an observer event looks like. 

Let's setup a basic example and log the events to the console. I won't go into details as to what each value on the event does or stands for, you can go read up the details [here](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).

```javascript
const observer = new IntersectionObserver(callback);
const img = document.getElementById('lazy-img');
observer.observe(img);

function callback(changes){
    console.log(changes)
    changes.forEach(change => {
        console.log(change)
    })
}
``` 
The observer typically returns a list of IntersectionObserverEntry objects containing metadata about the changes of a target element.


If we want to check if an element is fully visible on the viewport, our obeserver callback will look like this:

```javascript
const observer = new IntersectionObserver(callback);
const img = document.getElementById('lazy-img');
observer.observe(img);

function callback(changes){
    console.log(changes)
    changes.forEach(change => {
        if(change.intersectionRatio >= 1){
            img.classList.add('visible')
        }
        else{
            img.classList.add('not-visible')
        }
    })
}
```

**How do we lazy load with angularJS already??**

Easy there! Getting to it. 

So to add this to our angularJS project, we'll create a simple directive that adds our img src when it enters the viewport. Since our image won't have a src attribute just yet, we can add height and background color styles to it.

```css
img{
    height: 60px;
    background: grey;
}
```

then our javascript code should look like this:

```javascript
angular
    .module('lazy', [])
    .directive('lazyLoad', lazyLoad)

function lazyLoad(){
    return {
        restrict: 'A',
        link: function(scope, element, attrs){
            const observer = new IntersectionObserver(loadImg)
            const img = angular.element(element)[0];
            observer.observe(img)

            function loadImg(changes){
                changes.forEach(change => {
                    if(change.intersectionRatio > 0){
                        change.target.src = 'boy.jpg'
                    }
                })
            }    
    
        }
    }
}
```

Our HTML is next. It should resemble this:

```html
<body ng-app="lazy">
    <div>
        <img alt="" class="" lazy-load>
    </div> 
</body>

```

And that's it!! Obviously it's not elegant enough, you could go a step further by adding a low res copy of your image and then replacing it with your high res copy when the image is coming into view. We could also add a medium like blur on the image and then reveal the image when it comes into view.

Let's replace our current code with the following below to make the process a bit more elegant.

CSS

```css

.img-blur{
    filter: blur(10px);
}

img{
    height: 60px;
    background: gray;
}
```

HTML

```html
<body ng-app="lazy">
    <div>
        <img src="low-res.jpg" class="img-blur" alt="Lazy load" lazy-load>
    </div>
<body>
```

JS

```javascript

angular
    .module('lazy', [])
    .directive('lazyLoad', lazyLoad)

function lazyLoad(){
    return {
        restrict: 'A',
        link: function(scope, element, attrs){
            const observer = new IntersectionObserver(loadImg)
            const img = angular.element(element)[0];
            observer.observe(img)

            function loadImg(changes){
                changes.forEach(change => {
                    if(change.intersectionRatio > 0){
                        change.target.src = 'boy.jpg';
                        change.target.classList.remove('img-blur');
                    }
                })
            }    
    
        }
    }
}
```

And that's it, we have a decent enough process of lazy loading our images using the  IntersectionObserver API and angular's directives. I'm sure there are several ways to better this process and you can shoot your ideas in the comment section below.