Christophe KIFFEL
Christophe KIFFEL

Reputation: 41

Force reload cached image with same url after dynamic DOM change

I'm developping an angular2 application (single page application). My page is never "reloaded", but it's content changes according to user interactions.

I'm having some cache problems especially with images.

Context :

My page contains an editable image list :

<ul>
<li><img src="myImageController/1"><a href="editPage/1">Edit</a></li>
<li><img src="myImageController/2"><a href="editPage/2">Edit</a></li>
<li><img src="myImageController/3"><a href="editPage/3">Edit</a></li>
</ul>

When i want to edit an image (Edit link), my dom content is completly changed to show another angular component with a fileupload component.

The myImageController returns the LastModified header, and cache-control : no-cache and must-revalidate.

After a refresh (hit F5), my page does a request to get all img src, which is correct : if image has been modified, it is downloaded, if not, i just get a 304 which is fine.

Note : my images are stored in database as blob fields.

Problem :

When my page content is dynamically reloaded with my single page app, containing img tags, the browser do not call a GET http request, but immediatly take image from cache. I assume this a browser optimization to avoid getting the same resource on the same page multiple times.

Wrong solutions :

The first solution is to add something like ?time=(new Date()).getTime() to generate unique urls and avoid browser cache. This won't send the If-Modified-Since header in the request, and i will download my image every time completly.

Do a "real" refresh : the first page load in angular apps is quite slow, and i don't to refresh all.

Tests

To simplify the problem, i trying to create a static html page containing 3 images with the exact same link to my controller : /myImageController/1. With the chrome developper tool, i can see that only one get request is called. If i manage to get mulitple server calls in this case, it would probably solve my problem.

Thank you for your help.

Upvotes: 0

Views: 3305

Answers (2)

Kevin Cox
Kevin Cox

Reputation: 3212

You can use the random ID trick. This changes the URL so that the browser reloads the image. Not that this can be done in the query parameters to force a full cache break or in the hash to allow the browser to re-validate the image from the cache (and avoid re-downloading it if unchanged).

function reloadWithCache(img: HTMLImageElement, url: string) {
  img.src = url.replace(/#.*/, "") + "#" + Math.random();
}

function reloadBypassCache(img: HTMLImageElement, url: string) {
  let sep = img.indexOf("?") == -1? "?" : "&";
  img.src = url + sep + "nocache=" + Math.random()
}

Note that if you are using reloadBypassCache regularly you are better off fixing your cache headers. This function will always hit your origin server leading to higher running costs and making CDNs ineffective.

Upvotes: 0

Lionia Vasilev
Lionia Vasilev

Reputation: 12748

5th version of HTML specification describes this behavior. Browser may reuse images regardless of cache related HTTP headers. Check this answer for more information. You probably need to use XMLHttpRequest and blobs. In this case you also need to consider Same-origin policy.

You can use following function to make sure user agent performs every request:

var downloadImage = function ( imgNode, url ) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "blob";
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
      if (xhr.status == 200 || xhr.status == 304) {
        var blobUrl = URL.createObjectURL(xhr.response);
        imgNode.src = blobUrl;
        // You can also use imgNode.onload callback to release blob resources.
        setTimeout(function () {
          URL.revokeObjectURL(blobUrl);
        }, 1000);
      }
    }
  };
  xhr.send();
};

For more information check New Tricks in XMLHttpRequest2 article by Eric Bidelman, Working with files in JavaScript, Part 4: Object URLs article by Nicholas C. Zakas and URL.createObjectURL() MDN page and Same-origin policy MDN page.

Upvotes: 1

Related Questions