Lazy image loader for Angular > v2
npm install ng-lazyload-image![Status]()
!npm


!Build Status
A super small libary for lazy loading images for Angular apps with zero dependencies
- Demo
- Prerequisites
- Install
- Setup
- Usages
- Debug
- CSS
- API
- Hooks
- Search Engine Optimization (SEO)
- FAQ
- Contributing
Visit this site: https://naughty-bose-ec1cfc.netlify.com
The browser you are targeting need to have support of WeakMap and String.prototype.includes. If you need to support an older browser (like IE) you will need to include polyfill for WeakMap and String.prototype.includes (see https://github.com/zloirock/core-js for example).
Make also sure to inclue a pollyfill for IntersectionObserver if you need to target IE and want to use IntersectionObserver: https://github.com/w3c/IntersectionObserver/tree/master/polyfill
To install the package, just run:
```
$ npm install ng-lazyload-image
or the following if you are using yarn
``
$ yarn add ng-lazyload-image
Include the library in your module (see app.module.ts):
`javascript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { LazyLoadImageModule } from 'ng-lazyload-image'; // <-- import it
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, LazyLoadImageModule], // <-- and include it
bootstrap: [AppComponent],
})
export class MyAppModule {}
`
ng-lazyload-image is using a intersection observer by default so you don't need to do anything if you want to continue using the intersection observer as event emitter.
You can easily swtich from IntersectionObserver to scroll listener by using the scrollHooks:
`javascript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { LazyLoadImageModule, LAZYLOAD_IMAGE_HOOKS, ScrollHooks } from 'ng-lazyload-image'; // <-- include ScrollHooks
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, LazyLoadImageModule],
providers: [{ provide: LAZYLOAD_IMAGE_HOOKS, useClass: ScrollHooks }], // <-- Declare that you want to use ScrollHooks
bootstrap: [AppComponent],
})
export class MyAppModule {}
`
See hooks below for more information.
A simple usecase is to use a img-tag and give it the image to lazyload to [lazyLoad] and an optional default image to [defaultImage]. The default image will be shown while the "real" image is getting loaded.
Example:
`javascript
import { Component } from '@angular/core';
@Component({
selector: 'image',
template:
,`
})
class ImageComponent {
defaultImage = 'https://www.placecage.com/1000/1000';
image = 'https://images.unsplash.com/photo-1443890923422-7819ed4101c0?fm=jpg';
}
It also supports background images:
`javascript
@Component({
selector: 'image',
template:
,
})
class ImageComponent {
defaultImage = 'https://www.placecage.com/1000/1000';
image = 'https://images.unsplash.com/photo-1443890923422-7819ed4101c0?fm=jpg';
}
`$3
If using responsive images in a plain
tag, you'll need to set the useSrcset attribute to true:`javascript
@Component({
selector: 'image',
template: ,
})
class ImageComponent {
defaultImage = 'https://www.placecage.com/1000/1000';
images = https://images.unsplash.com/photo-1434725039720-aaad6dd32dfe?fm=jpg 700w,;
}
`If using responsive images in a
tag, set the default tag as usual with lazyLoad etc. attributes.
You can use attr.lazyLoad, attr.defaultImage and attr.errorImage attributes for elements.
There's no need to set useSrcset for elements, as srcset is used by default.
A simple example for a tag:`javascript
@Component({
selector: 'image',
template: ,
})
class ImageComponent {
screen_lg = '1200px';
screen_md = '992px';
defaultImage = 'https://www.placecage.com/1000/1000';
image1 = 'https://images.unsplash.com/photo-1422004707501-e8dad229e17a?fm=jpg';
image2 = 'https://images.unsplash.com/photo-1439931444800-9bcc83f804a6?fm=jpg';
image3 = 'https://images.unsplash.com/photo-1417128281290-30a42da46277?fm=jpg';
}
`$3
You can load image async or change the url on the fly, eg.
`html
![]()
`$3
Sometimes you want to get more controll over when the we should check if the image is in viewport.
customObservable let's you create your own observable.This will change the functionality of when we chek if the image is in the viewport. It does not change the functionality of how to detect if an image is in the viewport or not. Meaning: if you are using IntersectionObserver (default), it is important that the obserer that you pass to
customObservable will emit objects that looks like: { isIntersecting: boolean }. You can change this behavior by implementing your own isVisible (see hooks below for more information).If you are using the ScrollHooks-preset, you can just pass
customObservable and the reset will be handle automatically.`ts
import { merge, fromEvent } from 'rxjs'...
constructor() {
this.scroll$ = merge(
fromEvent(window, 'scroll'),
fromEvent(someDivRef, 'scroll')
);
}
``html
![]()
`$3
If you are using Ionic and don't want to use IntersectionObserver, you may need to include your own scroll observable or change the scroll target. For instans if you want to have multiple scroll targets:
`javascript
@Component({
selector: 'page-image',
template: ,
})
export class AboutPage {
lazyLoadImage = 'https://hd.unsplash.com/photo-1431400445088-1750c997c6b5';
}
`In case of using ion-slides in Ionic 2+, you can include your own scroll observable as below.
`javascript
@Component({
selector: 'page-image',
template: ,
})
export class AboutPage {
lazyLoadImage = 'https://hd.unsplash.com/photo-1431400445088-1750c997c6b5';
}
`🐛 Debug
In order to get a better understanding of what is happening you can pass
[debug]="true" which will output some debug information in the web console.See onStateChange for more information about the diffrent output messages.
💅 CSS
The css class name
ng-lazyloading will automatically be added before the image is loaded and will be removed when the image has been loaded or if the image couldn't be loaded.The css class name
ng-lazyloaded will be added when the image is loaded (regardless of if the image could be loaded or not).The css class name
ng-failed-lazyloaded will be added when the image couldn't be loaded.🔄 API
##### lazyLoad
Type:
stringExample:
https://images.unsplash.com/photo-1443890923422-7819ed4101c0?fm=jpgThe image to be lazy loaded. This image will replace the default image (
defaultImage).`html
![]()
`##### defaultImage (optional)
Type:
stringExample:
https://www.placecage.com/1000/1000Path to default image. This image will be loaded right away.
You can also use
src attribute for img tag to define default image:
or
background-image property for non-image tags:
##### errorImage (optional)
Type:
stringExample:
https://i.imgur.com/XkU4Ajf.pngAn image to be loaded if failing to load
lazyLoad. Will load the default image (defaultImage) if absent.`html
![]()
`##### offset (optional)
Type:
numberExample:
100Default:
0Number of px a image should be loaded before it is in view port
`html
![]()
`##### scrollTarget (optional)
Type:
ElementExample:
document.getElementById('my-scroll-container')Default:
windowYou will need to set this property if you are using a scroll container and do not propagate the scroll event to window.
##### customObservable (optional)
Type:
ObservableExample:
Observable.fromEvent(myScrollContainer, 'scroll')You can pass your own observable if you need more control over the flow. Can be useful if integrating with other frameworks like ionic. See Custom Observable for more information.
##### useSrcset (optional)
Type:
booleanExample:
trueYou can set this to
true if you need to lazy load images with srcset attribute, instead of src.
tags are set to use srcset by default, so you don't need to set this option additionaly.##### decode (optional)
Type:
booleanExample:
trueYou can set this to
true, the image will be decoded before inserted into the DOM. This can be useful for large images.##### debug (optional)
type:
booleanExaple:
trueSee debug for more information.
$3
##### onStateChange (optional)
Type:
Function: (event: StateChange) => voidExample:
You can pass a callback function, which will be called when the image is getting into different state.
`ts
myCallbackFunction(event: StateChange) {
switch (event.reason) {
case 'setup':
// The lib has been instantiated but we have not done anything yet.
break;
case 'observer-emit':
// The image observer (intersection/scroll/custom observer) has emit a value so we
// should check if the image is in the viewport.
// event.data is the event in this case.
break;
case 'start-loading':
// The image is in the viewport so the image will start loading
break;
case 'mount-image':
// The image has been loaded successfully so lets put it into the DOM
break;
case 'loading-succeeded':
// The image has successfully been loaded and placed into the DOM
break;
case 'loading-failed':
// The image could not be loaded for some reason.
// event.data is the error in this case
break;
case 'finally':
// The last event before cleaning up
break;
}
}
`🎣 Hooks
It is possible to hook into the loading process by create your own functions.
For example, let's say you want to fetch an image with some custom headers. If so, you can create a custom hook to fetch the image:
`ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { LazyLoadImageModule, IntersectionObserverHooks, Attributes, LAZYLOAD_IMAGE_HOOKS } from 'ng-lazyload-image';
import { AppComponent } from './app.component';export class LazyLoadImageHooks extends IntersectionObserverHooks {
loadImage({ imagePath }: Attributes): Promise {
return fetch(imagePath, {
headers: {
Authorization: 'Bearer ...',
},
})
.then((res) => res.blob())
.then((blob) => URL.createObjectURL(blob));
}
}
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, LazyLoadImageModule],
providers: [{ provide: LAZYLOAD_IMAGE_HOOKS, useClass: LazyLoadImageHooks }],
bootstrap: [AppComponent],
})
export class MyAppModule {}
`You can even use other services by inject them. Lets say you want to use Angulars https class instead of
window.fetch:`ts
import { NgModule, Injectable } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { LazyLoadImageModule, IntersectionObserverHooks, Attributes, LAZYLOAD_IMAGE_HOOKS } from 'ng-lazyload-image';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AppComponent } from './app.component';@Injectable()
export class LazyLoadImageHooks extends IntersectionObserverHooks {
private http: HttpClient;
constructor(http: HttpClient) {
super();
this.http = http;
}
loadImage({ imagePath }: Attributes): Observable {
// Load the image through
HttpClient and cancel the request if the user change page or the image gets removed
return this.http.get(imagePath, { responseType: 'blob' }).pipe(map(blob => URL.createObjectURL(blob)));
}
}@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule, LazyLoadImageModule],
providers: [{ provide: LAZYLOAD_IMAGE_HOOKS, useClass: LazyLoadImageHooks }],
bootstrap: [AppComponent],
})
export class MyAppModule {}
`The following hooks are supported:
$3
Should return an observable that emits a new value every time
ng-lazyload-image should check if the image is in viewport. It is important that the emited event is of the same type isVisible expects.Eg.
`ts
import { Attributes, IntersectionObserverHooks } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
getObservable(attributes: Attributes) {
// Will emit
{ isIntersecting: true } every second.
// You will have to overwride isVisible if you want to pass another event
return interval(1000).pipe(mapTo({ isIntersecting: true })));
}
}
`A more usefull example could be to add a debounce time so we only start loading the image if it has been in the view port for some time:
`ts
import { Attributes, IntersectionObserverHooks } from 'ng-lazyload-image';
import { debounceTime } from 'rxjs/operators';class LazyLoadImageHooks extends IntersectionObserverHooks {
getObservable(attributes: Attributes) {
// Only load the image if it has been in the viewport for one second
return super.getObservable(attributes).pipe(debounceTime(1000))
}
}
`See intersection-listener.ts for example.
See
isVisible if you want to use your own event.$3
Function to check if the element is vissible.
Eg.
`ts
import { Attributes, ScrollHooks } from 'ng-lazyload-image';class LazyLoadImageHooks extends ScrollHooks {
isVisible(event: Event | string, { element, scrollContainer, offset }: Attributes) {
//
event is form getObservable
return isElementInViewport(element, scrollContainer, offset);
}
}
`If you want to create your own event, you can create both
getObservable and isVisible by extend SharedHooks:`ts
import { Attributes, SharedHooks } from 'ng-lazyload-image';class LazyLoadImageHooks extends SharedHooks {
getObservable(attributes: Attributes) {
return interval(1000);
}
isVisible(event: number, attributes: Attributes) {
//
event is 0,1,2,3,4,5,...
return isElementInViewport(element, scrollContainer, offset);
}
}
`$3
Function to load the image. It must return a path to the image (it can however be async, like the example below and/or return a observable).
`ts
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
loadImage({ imagePath }: Attributes): Promise {
return await fetch(imagePath)
.then((res) => res.blob())
.then((blob) => URL.createObjectURL(blob));
}
}
`If you don't want to load the image but instead let the browser load it for you, then you can just return the imagePath (We will however not know if the image can't be loaded and the error image will not be used):
`ts
class LazyLoadImageHooks extends IntersectionObserverHooks {
loadImage({ imagePath }: Attributes) {
return [imagePath];
}
}
`$3
A function to set the image url to the DOM.
Eg.
`ts
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
setLoadedImage({ element, imagePath }: Attributes) {
//
imagePath comes from loadImage
element.src = imagePath;
}
}
`$3
This function will be called if the lazy image cant be loaded.
Eg.
`ts
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
setErrorImage({ element, errorImagePath }: Attributes) {
element.src = errorImagePath;
}
}
`$3
This function will be called on setup. Can be useful for (re)setting css-classes and setting the default image.
This function will be called every time an attrebute is changing.
Eg.
`ts
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
setup(attributes: Attributes) {
// Overwride the path to the default image for all lazyloaded images
attributes.defaultImagePath = 'some/path.jpg';
// You can always call the base
setup if you want to keep the default behavior
super.setup(attributes);
}
}
`$3
This function will be called on teardown. Can be useful for setting css-classes.
Eg.
`ts
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
finally(attributes: Attributes) {
// So something
}
}
`$3
A function to check if the current user is a bot or not. Can be useful for SSR and SEO.
Default function:
`ts
import { isPlatformServer } from '@angular/common';
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
isBot(attributes?: Attributes) {
// Check if the user is a bot or not.
this.navigator; // Is the same as
window.navigator if window is defined otherwise undefined.
isPlatformServer(this.platformId); // True if the code is running on the server
}
}
`Both
IntersectionObserverHooks and ScrollHooks will load the image as soon as possble if isBot returns true.$3
A function to decided if the module should be disabled, meaning: it should not do anything, just exit right away, without loading any image. The default behavior is to disable it on the server if the user is not a bot:
`ts
import { isPlatformServer } from '@angular/common';
import { IntersectionObserverHooks } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
isDisabled() {
// This is the same as:
return super.isDisabled();
return isPlatformServer(this.platformId) && !this.isBot();
}
}
`$3
A function to decided if we should load the image right away. This can be useful if you want to skip to lazyload the image when SSR:
`ts
import { isPlatformServer } from '@angular/common';
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
skipLazyLoading(attributes: Attributes) {
return isPlatformServer(this.platformId);
}
}
`Or maybe you want to load the image ASAP from specific domains
`ts
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
skipLazyLoading(attributes: Attributes) {
const imageUrl = new URL(attributes.imagePath);
return imageUrl.hostname === 'example.org';
}
}
`The default behavior for
skipLazyLoading is to call isBot. Meaning: if the user is a bot, load the image right away, otherwise, lazyload the image.$3
This function is called when some of the atrebute of the image is changed.
`ts
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
onAttributeChange(newAttributes: Attributes) {
console.log(
New attributes: ${newAttributes});
}
}
`$3
This function is called when a image is loaded and the directive will unmount, aka. when
ngOnDestroy is called in the directive. This can be useful if you want to do some cleanup.`ts
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
imageToBeLoaded = new Map();
onAttributeChange(newAttributes: Attributes) {
console.log(
New attributes: ${newAttributes});
imageToBeLoaded.set(newAttributes.id, newAttributes);
} onDestroy(attributes: Attributes) {
imageToBeLoaded.delete(attributes.id);
}
}
`🔎 Search Engine Optimization (SEO)
ng-lazyload-image are using the following strategy by default:$3
If the incoming request is from a bot; it will set
[lazyLoad] to src on the image (letting the browser loading the image right away). Useful if the bot don't understand javascript.If the request is not from a bot (or if we can't decide), don't do anything and let the client fix the images (see below).
You can chang this behavior by implementing your own
skipLazyLoading function (see skipLazyLoading above). Let's say you always want to show the image ASAP for SSR, regardles of if the user is a bot or not:`ts
import { isPlatformServer } from '@angular/common';
import { IntersectionObserverHooks, Attributes } from 'ng-lazyload-image';class LazyLoadImageHooks extends IntersectionObserverHooks {
skipLazyLoading(attributes: Attributes) {
// Skipping lazyloading the image for SSR
return isPlatformServer(this.platformId);
}
}
`$3
- If the user is a bot (see
isBot hook above), render all the images right away. (useful if the bot understand javascript)
- If the user is not a bot (or if we can't decide), lazy load the images🤔 FAQ
Q How can I manually trigger the loading of images?
A You can either use the
getObservable hook if you can trigger the loading outside the dom or you can use the customObservable input, see Custom ObservableQ Does this library work with ionic or some other wrapper for Angular?
A Yes, but ionic and some other library wraps the whole document inside an other div so you might need to create your own scroll listener.
Q How can I add a transition effect between the default image and the lazy loaded image?
A See: https://github.com/tjoskar/ng-lazyload-image/issues/300
Q It doesn't work with
BrowserAnimationsModule`A Are you using the scroll preset? If so, take a look at this issue.
Q Can I add a debounce time before loading the image?
A Yes, take a look at this issue.
Q Can I cancel image loading when the user change page?
A Yes, take a look at this issue.
Q I can't get it to work. Can you help me?
A Sure, create an issue and describe your issue in as much detail as possible.
See the contributing guide