Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazyloading images for urls that are not yet ready? #129

Closed
donjae opened this issue Mar 23, 2017 · 13 comments
Closed

Lazyloading images for urls that are not yet ready? #129

donjae opened this issue Mar 23, 2017 · 13 comments

Comments

@donjae
Copy link

donjae commented Mar 23, 2017

Hi,
Loving the package first off!

I wanted to ask though if it is possible to lazy load images for urls that have yet to be generated. For example, I have users submitting things, but the static URL serving the images aren't ready yet (GET 404 error).

Is there a possible to lazyload/display images those images when the url is ready. Perhaps by showing either the defaultImage/errorImage until the lazyloaded images work?

@donjae donjae changed the title Lazyloading images for urls that are not generated yet? Lazyloading images for urls that are not yet ready? Mar 23, 2017
@tjoskar
Copy link
Owner

tjoskar commented Mar 23, 2017

Hi,

The directive do not currently support to reload the images when the url changes (become ready), I will however accept a pull request for that :)

What you could do in the meantime is something like this:

<img *ngIf="image" [lazyLoad]="image">

or if you have a default image:

<img *ngIf="!image" [src]="defaultImage">
<img *ngIf="image" [lazyLoad]="image" [defaultImage]="defaultImage">

@born2net
Copy link

born2net commented Mar 30, 2017

I was just looking for exact same functionality :/
a url which is not available right away... need polling

@born2net
Copy link

@donjae did you ever find a solution? write your own?

@born2net
Copy link

@tjoskar tried

<img *ngIf="!image" [src]="defaultImage">
<img *ngIf="image" [lazyLoad]="image" [defaultImage]="defaultImage">

no luck, as it may take up to 10 seconds for my images to be available on the server

@donjae
Copy link
Author

donjae commented Mar 31, 2017

As for my application, I actually just set an [errorImage] as the default for now :/

@born2net
Copy link

mmm ok tx :/

@tjoskar
Copy link
Owner

tjoskar commented Apr 1, 2017

@donjae
Ahhh... I think I misunderstood you.
You have an URL to an image that returns 404 for some reason and you want to poll for changes? So if the library didn't succeed to fetch the image, you want the library to try again in, let's say 2 sec?
I don't think it is something that I want to support (or maybe I will, if you can come up with something clever in:

return Observable.of(1);
with retryWhen).

I think a better solution is to create your own scroll-event-emitter that emits a new value when the image is available.

@born2net
Copy link

born2net commented Apr 1, 2017

ok tx!

@born2net
Copy link

born2net commented Apr 1, 2017

I ended up develping my own as I didn't need the scrolling,
here it is:

import {Directive, ElementRef, EventEmitter, Input, NgZone, Output} from "@angular/core";
import {Observable} from "rxjs/Observable";
import {Subject} from "rxjs/Subject";

@Directive({
    selector: '[lazyImage]'
})
export class LazyImage {

    private m_url;
    private cancel$ = new Subject();

    constructor(private el: ElementRef, private ngZone: NgZone) {
    }

    @Input() defaultImage: string;
    @Input() loadingImage: string;
    @Input() errorImage: string;
    @Input() retry: number = 10;
    @Input() delay: number = 500;


    @Input()
    set url(i_url: string) {
        this.m_url = i_url;
        this.loadImage(i_url);
    }

    @Output() loaded: EventEmitter<any> = new EventEmitter<any>();
    @Output() completed: EventEmitter<any> = new EventEmitter<any>();
    @Output() errored: EventEmitter<any> = new EventEmitter<any>();

    set setUrl(i_url) {
        this.m_url = i_url;
        this.loadImage(i_url);
    }

    public resetToDefault() {
        this.setImage(this.el.nativeElement, this.defaultImage);
        this.cancel$.next({})
    }

    ngAfterViewInit() {
        this.setImage(this.el.nativeElement, this.defaultImage);
    }

    ngOnInit() {
    }

    setImage(element: HTMLElement, i_url) {
        // const isImgNode = element.nodeName.toLowerCase() === 'img';
        // if (isImgNode) {
        // } else {
        //     element.style.backgroundImage = `url('${imagePath}')`;
        // }
        (<HTMLImageElement>element).src = i_url;
        return element;
    }

    loadImage(i_url) {
        const pollAPI$ = Observable.defer(() => {
            return new Promise((resolve, reject) => {
                const img = new Image();
                img.src = i_url;
                img.onload = () => {
                    resolve(i_url);
                };
                img.onerror = err => {
                    this.setImage(this.el.nativeElement, this.loadingImage);
                    reject(err)
                };
            })


        }).retryWhen(err => {

            return err.scan((errorCount, err) => {
                if (errorCount >= this.retry) {
                    throw err;
                }
                return errorCount + 1;
            }, 0).delay(this.delay);
        }).takeUntil(this.cancel$)

        pollAPI$.subscribe((v) => {
            this.setImage(this.el.nativeElement, this.m_url)
            this.loaded.emit();
        }, (e) => {
            this.setImage(this.el.nativeElement, this.errorImage);
            this.errored.emit();
            // console.error(e)
        }, () => {
            this.completed.emit();
        })

    }

    destroy() {
    }
}

and to use it:

 <img lazyImage class="center-block" style="width: 229px; height: 130px"
     [loadingImage]="'https://secure.digitalsignage.com/studioweb/assets/screen_loading.png'"
     [defaultImage]="'https://secure.digitalsignage.com/studioweb/assets/screen.png'"
    [errorImage]="'https://secure.digitalsignage.com/studioweb/assets/screen_error.png'"
    [retry]="5"
    [delay]="1500"
    (loaded)="_onLoaded()"
    (error)="_onError()"
    (completed)="_onCompleted()">

and you can also load the image via API:

...

@ViewChild(LazyImage)
    lazyImage: LazyImage;
...

this.lazyImage.resetToDefault();
this.lazyImage.url = 'http://www.example.com/foo.png

@born2net
Copy link

born2net commented Apr 1, 2017

latest revision:
https://github.com/born2net/studioweb/blob/master/src/comps/lazy-image/lazy-image.ts

@donjae
Copy link
Author

donjae commented Apr 3, 2017

@tjoskar do you want to merge/implement what @born2net created? no problem if not, and I can close this issue out

@tjoskar
Copy link
Owner

tjoskar commented May 12, 2017

(sorry for late reply)
@donjae, I will probably not implement this but I will accept a pull request.
I guess it should be possible to replace line 73 with something like:

.mergeMap(() => {
  return loadImage(imagePath)
    .retryWhen(errors => {
        return errors.scan((errorCount, err) => {
            if(errorCount >= 5) {
                throw err;
            }
            return errorCount + 1;
        }, 0)
      .delay(1000);
    })
})

@tjoskar
Copy link
Owner

tjoskar commented Nov 5, 2017

I will close this issue but I will still accept pull request if someone wants to implement it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants