Ng-lazyload-image: Feature request: Get access to the image in observable creation

Created on 3 Feb 2018  ·  4Comments  ·  Source: tjoskar/ng-lazyload-image

Motivation and implementation

Right now it's possible to pass an observable which is a great way to extend the library. However, it is a bit limited since the uses can't get access to the image in a simple way.

One of the most common issues is when the user doesn't use the window to scroll or using a 3-part library that adds extra scroll layers (eg. ionic, ng material design, etc), and it can sometimes be hard to find out which element that actually scrolls. One way to solve this could be to use an Intersection Observer (which also can give a performance boost). Eg.

@Component({
  selector: 'intersection-observer',
  template: `<img #img [defaultImage]="defaultImage" [lazyLoad]="image" [scrollObservable]="subject">`,
})
export class IntersectionObserverComponent {
  @ViewChild('img') img;
  subject = new Subject();
  defaultImage = 'https://www.placecage.com/1000/1000';
  image = 'https://images.unsplash.com/photo-1467932760935-519284fc87fa?dpr=2&auto=compress,format&fit=crop&w=1199&h=800&q=80';

  ngAfterViewInit() {
    const config = {
      rootMargin: '50px 50px',
      threshold: 0.01
    };

    function onIntersection(entries) {
      entries.forEach(entry => {
        if (entry.intersectionRatio > 0) {
          observer.unobserve(entry.target);
          this.subject.next();
        }
      });
    }
    const observer = new IntersectionObserver(onIntersection, config);
    observer.observe(this.img.nativeElement);
  }
}

This is however quite cumbersome.

What we can do is to accept a function as scrollObservable, something like:

if (!this.scrollObservable) {
  const windowTarget = isWindowDefined() ? window : undefined;
  scrollObservable = getScrollListener(this.scrollTarget || windowTarget);
} else if (typeof this.scrollObservable === 'function') {
  scrollObservable = this.scrollObservable(image, offset) // and maybe more
} else {
  scrollObservable = this.scrollObservable.startWith('');
}

We can not rewrite the component above to:

@Component({
  selector: 'intersection-observer',
  template: `<img [defaultImage]="defaultImage" [lazyLoad]="image"
 [scrollObservable]="startLazyload" offest="50">`,
})
export class IntersectionObserverComponent {
  defaultImage = 'https://www.placecage.com/1000/1000';
  image = 'https://images.unsplash.com/photo-1467932760935-519284fc87fa?dpr=2&auto=compress,format&fit=crop&w=1199&h=800&q=80';

  startLazyload(image, offset) {
    Observable.create(observer => {
      const intersectionObserver = new IntersectionObserver(entries => {
        entries.forEach(entry => {
          if (entry.intersectionRatio > 0) {
            observer.next();
          }
        });
      }, {
        rootMargin: offset ? `${offset}px` : undefined,
        threshold: 0.01
      });
      intersectionObserver.observe(image);
      return () => observer.unobserve(image);
    });
  }
}

Better but still a bit noisy, especially when the user wants to use IntersectionObserver on all images.

A way to solve this could be too pass a default Observable in NgModule:

import { intersectionObserverLazyLoad } from 'some-place';

@NgModule({
    declarations: [ AppComponent ],
    imports: [ BrowserModule, lazyLoadImage({
      observable: intersectionObserverLazyLoad
    })],
    bootstrap: [ AppComponent ]
})
export class MyAppModule {}

We can no rewrite the component above to:

@Component({
  selector: 'intersection-observer',
  template: `<img [defaultImage]="defaultImage" [lazyLoad]="image" offest="50">`,
})
export class IntersectionObserverComponent {
  defaultImage = 'https://www.placecage.com/1000/1000';
  image = 'https://images.unsplash.com/photo-1467932760935-519284fc87fa?dpr=2&auto=compress,format&fit=crop&w=1199&h=800&q=80';
}

The ability to pass default functions in NgModule could also open up doors to create custom functions for isVisible and loadImage but that is a later story.

Would solve: #287, #195, #286, #285, #275, #259 and more. Most of the issues are solved in other ways but it would be a nice if we could give the user more flexibility when creating an observable.

What do you think @sapierens, @rimlin?

help wanted

Most helpful comment

@tjoskar as i understand you want to enhance scrollObservable by provide function as option for scrollObservable, which will return Observable, which is responsible to emit image load event? If it is, i think is really cool idea!
But i think is better to use object instead of enumerate arguments:

...
} else if (typeof this.scrollObservable === 'function') {
  scrollObservable = this.scrollObservable({
    image, 
    offset
  });
} else {
...

All 4 comments

@tjoskar as i understand you want to enhance scrollObservable by provide function as option for scrollObservable, which will return Observable, which is responsible to emit image load event? If it is, i think is really cool idea!
But i think is better to use object instead of enumerate arguments:

...
} else if (typeof this.scrollObservable === 'function') {
  scrollObservable = this.scrollObservable({
    image, 
    offset
  });
} else {
...

provide function as option for scrollObservable, which will return Observable, which is responsible to emit image load event

Correct, I may also want to rename scrollObservable but that would be a breaking change.

i think is better to use object instead of enumerate arguments

Agree!

I think this is a nice idea, especially passing a default observer.

i think is better to use object instead of enumerate arguments

I also agree with this.

Fixed in #365 and in version 2.1.0

Was this page helpful?
0 / 5 - 0 ratings

Related issues

el-davo picture el-davo  ·  4Comments

lares83 picture lares83  ·  3Comments

philipgiuliani picture philipgiuliani  ·  11Comments

sandeepdussa picture sandeepdussa  ·  9Comments

alisahinozcelik picture alisahinozcelik  ·  4Comments