Skip to content

Commit

Permalink
feat(image-loader): Set image asynchronously and placeholder immediately
Browse files Browse the repository at this point in the history
  • Loading branch information
MitkoTschimev authored and edoparearyee committed Feb 5, 2019
1 parent d03a9d5 commit d5ab91f
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 11 deletions.
16 changes: 16 additions & 0 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ <h3>Bottom image</h3>
#imgEl>
</sn-image-loader>
<h3>Bottom image imageLoaded event count: <span class="image-loaded-count">{{ imageLoadedEventCount }}</span></h3>

<h1>Scroll down for async image</h1>
<h3>Async image placeholder loaded: <span class="placeholder-loaded">{{ placeholderAsyncImageLoaded }}</span></h3>
<div class="spacer"></div>

<h3>Async image data after 3 seconds (placeholder immediately)</h3><button (click)="startAsyncLoading()">Start timer</button>
<sn-image-loader
[image]="asyncImage"
[sizes]="sizes"
imgClass="img img--bottom"
alt="lorem ipsum"
(placeholderLoaded)="onAsyncImagePlaceholderLoad($event)"
(imageLoaded)="onAsyncImageLoad($event)"
class="sn-image-loader sn-image-loader--bottom">
</sn-image-loader>
<h3>Async image imageLoaded event count: <span class="image-loaded-count">{{ asyncImageLoadedEventCount }}</span></h3>
<div class="spacer"></div>

<sn-video-loader
Expand Down
75 changes: 73 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';

import { Breakpoint } from './image-loader/shared/breakpoint.model';
import { ResponsiveImage, Size } from './image-loader/shared/image.model';
Expand All @@ -11,10 +11,11 @@ import { video, image, sizes } from './app-data';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
export class AppComponent implements OnInit {
sizes: Breakpoint[] = sizes;
image: ResponsiveImage = image;
video: ResponsiveVideo = video;
asyncImage: ResponsiveImage;

/**
* Set to true on placeholder loaded event.
Expand All @@ -24,6 +25,14 @@ export class AppComponent {
*/
placeholderLoaded = false;

/**
* Set to true on placeholder loaded event.
*
* @type {boolean}
* @memberof AppComponent
*/
placeholderAsyncImageLoaded = false;

/**
* Incremented on each image load event.
*
Expand All @@ -32,6 +41,14 @@ export class AppComponent {
*/
imageLoadedEventCount = 0;

/**
* Incremented on each async image load event.
*
* @type {number}
* @memberof AppComponent
*/
asyncImageLoadedEventCount = 0;

/**
* Increments event count on each image loaded event.
* Counter displayed in component template.
Expand All @@ -42,6 +59,16 @@ export class AppComponent {
this.placeholderLoaded = true;
}

/**
* Increments event count on each image loaded event.
* Counter displayed in component template.
*
* @memberof AppComponent
*/
public onAsyncImagePlaceholderLoad(imageLoadedEvent: ImageLoadedEvent) {
this.placeholderAsyncImageLoaded = true;
}

/**
* Increments event count on each image loaded event.
* Counter displayed in component template.
Expand All @@ -51,4 +78,48 @@ export class AppComponent {
public onImageLoad(imageLoadedEvent: ImageLoadedEvent) {
this.imageLoadedEventCount++;
}

/**
* Increments event count on each image loaded event.
* Counter displayed in component template.
*
* @memberof AppComponent
*/
public onAsyncImageLoad(imageLoadedEvent: ImageLoadedEvent) {
this.asyncImageLoadedEventCount++;
}

/**
* Resets async image data and simulates loading
*
* @memberof AppComponent
*/
public startAsyncLoading() {
this.asyncImageLoadedEventCount = 0;
this.placeholderAsyncImageLoaded = false;
this.loadAsyncImageData();
}

ngOnInit() {
this.asyncImage = {
placeholder: image.placeholder,
images: [],
fallback: image.fallback,
};
}

/**
* Sets a placeholder for the image and simulates loading of image data (typically from some API endpoint)
* After 3 seconds the image data is set
*/
private loadAsyncImageData() {
this.asyncImage = {
placeholder: image.placeholder,
images: [],
fallback: image.fallback,
};
setTimeout(() => {
this.asyncImage = image;
}, 3000);
}
}
41 changes: 41 additions & 0 deletions src/app/image-loader/image-loader/image-loader.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,45 @@ describe('ImageLoaderComponent', () => {
component.ngOnChanges();
expect(spy).toHaveBeenCalled();
});

describe('async image', () => {
let spyPreloadImage, spySetPlaceholder;

beforeEach(() => {
spyPreloadImage = spyOn(component, 'preloadImage').and.callThrough();
spySetPlaceholder = spyOn(component, 'setPlaceholder').and.callThrough();

component.image = {
placeholder: 'someplaceholder',
images: null,
fallback: null,
};
component.inViewport = true;
component.loaded = false;
});

it('should not preload image if no images are set and is in viewport', () => {
component.ngOnChanges();
expect(spyPreloadImage).toHaveBeenCalled();
expect(spySetPlaceholder).toHaveBeenCalled();
expect(component.preloadSrc).toEqual('');
expect(component.preloadSrcset).toEqual('');
});

it('should preload image after already scrolled in and set later on', () => {
component.ngOnChanges();
expect(component.preloadSrc).toEqual('');
expect(component.preloadSrcset).toEqual('');
expect(component.src).toEqual('someplaceholder');

component.image = image;
component.ngOnChanges();
expect(component.preloadSrc).toEqual(
'http://via.placeholder.com/150x350?text=xs+1x',
);
expect(component.preloadSrcset).toEqual(
'http://via.placeholder.com/150x350?text=xs+1x 1x, http://via.placeholder.com/300x700?text=xs+2x 2x',
);
});
});
});
20 changes: 11 additions & 9 deletions src/app/image-loader/image-loader/image-loader.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,18 @@ export class ImageLoaderComponent
*/
public preloadImage(): void {
if (this.inViewport && this.notLoaded) {
const retinaImg = this.image.images.find(
retinaImage => retinaImage.size === this.size,
);
const imageNormal = retinaImg.x1;
const imageRetina = retinaImg.x2;
if ('srcset' in this.img.nativeElement) {
this.supportsSrcSet = true;
const retinaImg =
this.image.images &&
this.image.images.find(retinaImage => retinaImage.size === this.size);
if (retinaImg) {
const imageNormal = retinaImg.x1;
const imageRetina = retinaImg.x2;
if ('srcset' in this.img.nativeElement) {
this.supportsSrcSet = true;
}
this.preloadSrcset = `${imageNormal} 1x, ${imageRetina} 2x`;
this.preloadSrc = imageNormal;
}
this.preloadSrcset = `${imageNormal} 1x, ${imageRetina} 2x`;
this.preloadSrc = imageNormal;
}
}
/**
Expand Down

0 comments on commit d5ab91f

Please sign in to comment.