Skip to content

Instantly share code, notes, and snippets.

@Solomko2
Created May 13, 2018 17:23
Show Gist options
  • Save Solomko2/cd206d8da5250930167989952f39fbfa to your computer and use it in GitHub Desktop.
Save Solomko2/cd206d8da5250930167989952f39fbfa to your computer and use it in GitHub Desktop.
lazy loading for
import {
Input, Directive, ViewContainerRef,
OnInit, TemplateRef, DoCheck,
IterableDiffers, IterableDiffer
} from '@angular/core';
@Directive({
selector: '[lazyFor]'
})
export class LazyForDirective implements DoCheck, OnInit {
lazyForContainer: HTMLElement;
itemHeight: number;
itemTagName: string;
@Input()
set lazyForOf(list: any[]) {
this.list = list;
if (list) {
this.differ = this.iterableDiffers.find(list).create();
if (this.initialized) {
this.update();
}
}
}
private templateElem: HTMLElement;
private beforeListElem: HTMLElement;
private afterListElem: HTMLElement;
private list: any[] = [];
private initialized = false;
private firstUpdate = true;
private differ: IterableDiffer<any>;
private lastChangeTriggeredByScroll = false;
constructor(private vcr: ViewContainerRef,
private tpl: TemplateRef<any>,
private iterableDiffers: IterableDiffers) { }
ngOnInit() {
this.templateElem = this.vcr.element.nativeElement;
this.lazyForContainer = this.templateElem.parentElement;
//Adding an event listener will trigger ngDoCheck whenever the event fires so we don't actually need to call
//update here.
this.lazyForContainer.addEventListener('scroll', () => {
this.lastChangeTriggeredByScroll = true;
});
this.initialized = true;
}
ngDoCheck() {
if (this.differ && Array.isArray(this.list)) {
if (this.lastChangeTriggeredByScroll) {
this.update();
this.lastChangeTriggeredByScroll = false;
} else {
const changes = this.differ.diff(this.list);
if (changes !== null) {
this.update();
}
}
}
}
/**
* List update
*
* @returns {void}
*/
private update(): void {
//Can't run the first update unless there is an element in the list
if (this.list.length === 0) {
this.vcr.clear();
if (!this.firstUpdate) {
this.beforeListElem.style.height = '0';
this.afterListElem.style.height = '0';
}
return;
}
if (this.firstUpdate) {
this.onFirstUpdate();
}
const listHeight = this.lazyForContainer.clientHeight;
const scrollTop = this.lazyForContainer.scrollTop;
//The height of anything inside the container but above the lazyFor content
const fixedHeaderHeight =
(this.beforeListElem.getBoundingClientRect().top - this.beforeListElem.scrollTop) -
(this.lazyForContainer.getBoundingClientRect().top - this.lazyForContainer.scrollTop);
//This needs to run after the scrollTop is retrieved.
this.vcr.clear();
let listStartI = Math.floor((scrollTop - fixedHeaderHeight) / this.itemHeight);
listStartI = this.limitToRange(listStartI, 0, this.list.length);
let listEndI = Math.ceil((scrollTop - fixedHeaderHeight + listHeight) / this.itemHeight);
listEndI = this.limitToRange(listEndI, -1, this.list.length - 1);
for (let i = listStartI; i <= listEndI; i++) {
this.vcr.createEmbeddedView(this.tpl, {
$implicit: this.list[i],
index: i
});
}
this.beforeListElem.style.height = `${listStartI * this.itemHeight}px`;
this.afterListElem.style.height = `${(this.list.length - listEndI - 1) * this.itemHeight}px`;
}
/**
* First update.
*
* @returns {void}
*/
private onFirstUpdate(): void {
let sampleItemElem: HTMLElement;
if (this.itemHeight === undefined || this.itemTagName === undefined) {
this.vcr.createEmbeddedView(this.tpl, {
$implicit: this.list[0],
index: 0
});
sampleItemElem = <HTMLElement>this.templateElem.nextSibling;
}
if (this.itemHeight === undefined) {
this.itemHeight = sampleItemElem.clientHeight;
}
if (this.itemTagName === undefined) {
this.itemTagName = sampleItemElem.tagName;
}
this.beforeListElem = document.createElement(this.itemTagName);
this.templateElem.parentElement.insertBefore(this.beforeListElem, this.templateElem);
this.afterListElem = document.createElement(this.itemTagName);
this.templateElem.parentElement.insertBefore(this.afterListElem, this.templateElem.nextSibling);
// If you want to use <li> elements
if (this.itemTagName.toLowerCase() === 'li') {
this.beforeListElem.style.listStyleType = 'none';
this.afterListElem.style.listStyleType = 'none';
}
this.firstUpdate = false;
}
/**
* Limit To Range
*
* @param {number} num - Element number.
* @param {number} min - Min element number.
* @param {number} max - Max element number.
*
* @returns {number}
*/
private limitToRange(num: number, min: number, max: number) {
return Math.max(
Math.min(num, max),
min
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment