How to prevent Browser cache on Angular 2 site?

130,939

Solution 1

angular-cli resolves this by providing an --output-hashing flag for the build command (versions 6/7, for later versions see here). Example usage:

ng build --output-hashing=all

Bundling & Tree-Shaking provides some details and context. Running ng help build, documents the flag:

--output-hashing=none|all|media|bundles (String)

Define the output filename cache-busting hashing mode.
aliases: -oh <value>, --outputHashing <value>

Although this is only applicable to users of angular-cli, it works brilliantly and doesn't require any code changes or additional tooling.

Update

A number of comments have helpfully and correctly pointed out that this answer adds a hash to the .js files but does nothing for index.html. It is therefore entirely possible that index.html remains cached after ng build cache busts the .js files.

At this point I'll defer to How do we control web page caching, across all browsers?

Solution 2

Found a way to do this, simply add a querystring to load your components, like so:

@Component({
  selector: 'some-component',
  templateUrl: `./app/component/stuff/component.html?v=${new Date().getTime()}`,
  styleUrls: [`./app/component/stuff/component.css?v=${new Date().getTime()}`]
})

This should force the client to load the server's copy of the template instead of the browser's. If you would like it to refresh only after a certain period of time you could use this ISOString instead:

new Date().toISOString() //2016-09-24T00:43:21.584Z

And substring some characters so that it will only change after an hour for example:

new Date().toISOString().substr(0,13) //2016-09-24T00

Hope this helps

Solution 3

In each html template I just add the following meta tags at the top:

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">

In my understanding each template is free standing therefore it does not inherit meta no caching rules setup in the index.html file.

Solution 4

A combination of @Jack's answer and @ranierbit's answer should do the trick.

Set the ng build flag for --output-hashing so:

ng build --output-hashing=all

Then add this class either in a service or in your app.module

@Injectable()
export class NoCacheHeadersInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler) {
        const authReq = req.clone({
            setHeaders: {
                'Cache-Control': 'no-cache',
                 Pragma: 'no-cache'
            }
        });
        return next.handle(authReq);    
    }
}

Then add this to your providers in your app.module:

providers: [
  ... // other providers
  {
    provide: HTTP_INTERCEPTORS,
    useClass: NoCacheHeadersInterceptor,
    multi: true
  },
  ... // other providers
]

This should prevent caching issues on live sites for client machines

Solution 5

Add this to your nginx

location ~ /index.html|.*\.json$ {
        expires -1;
        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
    }

location ~ .*\.css$|.*\.js$ {
   add_header Cache-Control 'max-age=31449600'; # one year
}

location / {
    try_files $uri $uri/ /index.html?$args;
    add_header Cache-Control 'max-age=86400'; # one day
}
Share:
130,939
Rikku121
Author by

Rikku121

Being curious

Updated on November 26, 2021

Comments

  • Rikku121
    Rikku121 over 2 years

    We're currently working on a new project with regular updates that's being used daily by one of our clients. This project is being developed using angular 2 and we're facing cache issues, that is our clients are not seeing the latest changes on their machines.

    Mainly the html/css files for the js files seem to get updated properly without giving much trouble.

  • Rikku121
    Rikku121 over 7 years
    We have switched to webpack for some time now and it takes care of cache busting our angular apps. It's good to know your solution works though. Thanks
  • Rossco
    Rossco over 7 years
    So my implementation actually didn't end up working. caching is a strange issue. sometimes works and sometimes not. oh the beauty of intermittent issues. So I actually adapted your answer to as such: templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
  • Shenbo
    Shenbo about 7 years
    I'm getting 404 for my templateUrls. For example: GET localhost:8080/app.component.html/?v=0.0.1-alpha 404 (Not Found) Any idea why?
  • Shenbo
    Shenbo about 7 years
    @Rikku121 No it doesn't. It's actually without the / in the url. I might have accidentally added it in when I post the comment
  • Shenbo
    Shenbo about 7 years
    Turned out it could be because I have to give a full path rather than a relative path. For example: templateUrl: 'app/app.component.html?v=' + VERSION works. But templateUrl: './app.component.html?v=' + VERSION does not. Even though they are in the same directory
  • Rikku121
    Rikku121 about 7 years
    Then it's more of a why are my templates not loading question, thought you meant it wasn't loading after adding the ?v=... which was rather weird
  • Shenbo
    Shenbo about 7 years
    Yes that is what I meant. And yes that is quite weird @Rikku121
  • RAVI PATEL
    RAVI PATEL almost 7 years
    To use this i have to go through to each component and append that "?v=".I have around 60 components. So it tedious job to go each component and do that. Is there a global way so we can define one variable and we can use in each templateUrl. Because i don't want that user every time take html from disk if there is no change in html.
  • Rikku121
    Rikku121 almost 7 years
    @RAVIPATEL best way is to use webpack using angular CLI, it's working really well and you get aot compilation too
  • iniravpatel
    iniravpatel almost 7 years
    It did for me too
  • jonesy827
    jonesy827 over 6 years
    This is the proper way to do this and should be the selected answer!
  • DDiVita
    DDiVita over 6 years
    This did not work for our app. Its too bad the templateUrl with a query string parameter does not work with CLI
  • Apurv Kamalapuri
    Apurv Kamalapuri about 6 years
    What is the point of caching when you are busting cache every time even when there is no code change?
  • Paramvir Singh Karwal
    Paramvir Singh Karwal almost 6 years
    Totally busts the issue. --aot  — enable Ahead-of-Time compilation. This will become a default setting in future versions of Angular CLI but for now we have to enable this manually --output-hashing all — hash contents of the generated files and append hash to the file name to facilitate browser cache busting (any change to file content will result in different hash and hence browser is forced to load a new version of the file) [ source : medium.com/@tomastrajan/… ]
  • stryba
    stryba over 5 years
    This won't work if your index.html is cached by the browser, hence won't see new hashed names for your javascript resources. I think this a combination of this and the answer @Rossco gave would make sense. It also makes sense to make this consistent with HTTP headers sent.
  • OzzyTheGiant
    OzzyTheGiant over 5 years
    @stryba This is why html caching should be handled differenty. You should specify the Cache-Control, Pragma, and Expires response headers so that no caching should take place. This is easy if you are using a backend framework, but I believe you can also handle this in .htaccess files for Apache (idk how it works in nginx though).
  • Jayesh Dhandha
    Jayesh Dhandha about 5 years
    @ApurvKamalapuri I agree with you. I have similar kind of issue, I want to cache bust only when my application is updated and the new build is triggered.
  • Noppey
    Noppey about 5 years
    This answer adds a hash to the js files, which is great. But as stryba said, you also need to make sure index.html is not cached. You shouldn't do this with html meta tags, but with response header cache-control: no-cache (or other headers for more fancy caching strategies).
  • Pranjal Successena
    Pranjal Successena almost 5 years
    ng build --aot --build-optimizer=true --base-href=/<url>/ gives error --- Couldn't resolve resource ./login.component.html?v=${new Date().getTime()}
  • Dan Dohotaru
    Dan Dohotaru almost 5 years
    i hardly believe busting caching for no reason other than elapsed time is sufficient in this context
  • NiallMitch14
    NiallMitch14 over 4 years
    Perfect, thank you! Althought consider removing the --aot flag as this might not be applicable to everyone
  • Jack
    Jack over 4 years
    As per numerous recommendations, I've removed the --aot flag.
  • Omar Salem
    Omar Salem over 3 years
    I got an error ``` ERROR in HostResourceResolver: could not resolve ./settings.component.html?v=1 in context of /src/app/account/components/settings/settings.component.ts) ```
  • Alexus
    Alexus about 3 years
    I cannot believe this is the accepted answer. This is just the worst recommendation I have ever seen. I'd rather suggest solving this problem from your static content service perspective (Apache, Nginx, IIS, ... )
  • Danilo Körber
    Danilo Körber almost 3 years
    What if I am not using NGINX?
  • minigeek
    minigeek over 2 years
    I wonder why people haven't upvoted your answer. thanks mate. this index.html is making mess , ngnix is good way to handle it, fortunately i am using same for backend
  • Void
    Void over 2 years
    In some cases this solution is not enough and your browser will still load the old version.