0

I have an Angular component that is effectively showing a list of thumbnail images for a list of video streams:

<div *ngFor="let s of streamList;trackBy: streamListTrackFn">
  <div class="thumbnail">
    <img [attr.src]="loadThumbnail(s.thumbnailUrl) | async" />
   </div>
</div>

Previously I was directly binding [src]="{{s.thumbnailUrl}}" and as the streamList got periodically updated, I got a new list of images rendered with fresh thumbnails.

The service providing the thumbnails requires Basic authentication so I am trying to switch to loading the images with my own XHR request, that is why I changed to [attr.src]="loadThumbnail(..)" which returns an image data URI loaded asynchronously as an `Observable".

My problem is that on the network tab even without changes happening on the streamList which drives the *ngFor I am seeing a huge number of HTTP requests being shoot out and also cancelled. I assume this is digest cycle/binding thing in Angular.

What would be the most optimal way to load the thumbnails in this case without falling into this infinite loop of requesting the thumbnails over and over again? (The thumbnailUrl itself carries a ?t=123456 counter to help mitigating any caching issue)

How can I prevent Angular from doing a new loadThumbnail until the streamList really changes..? Or actually at least the s.thumbnailUrl?

Thanks!

jabal
  • 11,987
  • 12
  • 51
  • 99
  • Why don't you authenticate before looping through the images? – Hoyen Mar 15 '18 at 14:41
  • I am not aware of any way to get the Authentication header automatically attached to the IMG tag's native requests. If I open a thumbnail URL in a separate tab, the browser pops up the login box which I can fill in and then that fixes the other tab's IMG requests too, but that's not a good solution of course. – jabal Mar 15 '18 at 14:46
  • @jabal, you just need to change server configuration so that it would not require authentication on image resources. Doing it makes no sense to me (but maybe I'm missing something?). – Alexander Leonov Mar 15 '18 at 14:52
  • These are thumbnail images of live streams and they do need to be protected. – jabal Mar 15 '18 at 14:56

1 Answers1

0

Well, first of all it is definitely wrong to use such kind of function in the template binding. While angular refreshes html bindings it will evaluate them gazillion times, so those functions must be blazing fast and they most certainly must not go grab some external resources on the web. All the stuff that they use must be immediately available.

My guess is that you just need to bind attr.src attribute directly to s.thumbnailUrl (btw, what does loadThumbnail() function do?), but not seeing all the code around this component - guessing is the best I can do.

UPDATE

What I'd recommend is to change loadThumbnail() so that it would return image data instead of url (see here for example), call it in the same place where streamList gets assigned/updated, and bind to the object field containing that data - something like the code below. Anyway, it does not change the rest of the answer - html bindings is still wrong place to do this all. :)

// this is something like you probably have
this.someService.getStreamList().subscribe((sl) => {
    this.streamList = sl;

    // this is not elegant at all and can be done better for sure
    // but will do just to convey the idea
    Observable.fromArray(this.streamList).subscribe((s) => {
        this.loadThumbnail(s.thumbnailUrl).subscribe((data) => {
            s.thumbnail = data;
        });
    });
});

And then bind it in template something like this:

<div *ngFor="let s of streamList;trackBy: streamListTrackFn">
  <div class="thumbnail">
    <img [attr.src]="data:image/png;base64,{{s.thumbnail}}" />
   </div>
</div>
Alexander Leonov
  • 4,694
  • 1
  • 17
  • 25
  • `loadThumbnail` does load the image with an XHR and takes care of adding the Authorization header which would not get added otherwise. So if I did what you answered here, I would get 401 for the query sent by the img tag and nothing would work. – jabal Mar 15 '18 at 18:34