130 votes

Comment éviter le cache du navigateur sur le site Angular 2?

Nous travaillons actuellement sur un nouveau projet avec des mises à jour régulières qui est utilisé quotidiennement par l'un de nos clients. Ce projet est développé en utilisant Angular 2 et nous rencontrons des problèmes de cache, c'est-à-dire que nos clients ne voient pas les derniers changements sur leurs machines.

Principalement, les fichiers html/css pour les fichiers js semblent être mis à jour correctement sans causer trop de problèmes.

3 votes

Très bonne question. J'ai le même problème. Quel est le meilleur moyen de résoudre ce problème ? Est-ce possible avec gulp ou tout outil similaire pour publier une application Angular 2 ?

2 votes

@jump4791 La meilleure façon est d'utiliser webpack et de compiler le projet en utilisant les paramètres de production. Je suis actuellement en train d'utiliser ce dépôt, il suffit de suivre les étapes et tout devrait bien se passer : github.com/AngularClass/angular2-webpack-starter

0 votes

Je rencontre également le même problème.

203voto

Jack Points 647

angular-cli résout ce problème en fournissant un indicateur --output-hashing pour la commande de construction (versions 6/7, pour les versions ultérieures voir ici). Exemple d'utilisation :

ng build --output-hashing=all

Regroupement & Tree-Shaking fournit quelques détails et du contexte. En exécutant ng help build, la documentation de l'indicateur est affichée :

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

Définit le mode de hachage des noms de fichier en sortie.
aliases: -oh , --outputHashing 

Même si cela ne s'applique qu'aux utilisateurs de angular-cli, cela fonctionne parfaitement et ne nécessite aucun changement de code ou d'outils supplémentaires.

Mise à jour

Un certain nombre de commentaires ont utilement et correctement souligné que cette réponse ajoute un hash aux fichiers .js mais ne fait rien pour index.html. Il est donc tout à fait possible que index.html reste mis en cache après que ng build ait mis à jour les hashes des fichiers .js.

À ce stade, je renvoie à Comment contrôler la mise en cache des pages Web, sur tous les navigateurs ?

17 votes

Ceci est la bonne manière de le faire et devrait être la réponse sélectionnée!

1 votes

Cela n'a pas fonctionné pour notre application. C'est dommage que le templateUrl avec un paramètre de chaîne de requête ne fonctionne pas avec CLI

1 votes

Totally busts the issue. --aot —activer la compilation Ahead-of-Time. Ceci deviendra un paramètre par défaut dans les futures versions de Angular CLI mais pour le moment nous devons activer ceci manuellement --output-hashing all—hacher le contenu des fichiers générés et apposer le hachage au nom du fichier pour faciliter le cache browser busting (tout changement au contenu du fichier entraînera un hachage différent et donc le navigateur est contraint de charger une nouvelle version du fichier) [ source : medium.com/@tomastrajan/… ]

36voto

Rikku121 Points 903

Voici une manière de le faire, il vous suffit d'ajouter une chaîne de requête pour charger vos composants, comme ceci :

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

Cela devrait forcer le client à charger la copie du template du serveur au lieu de celui du navigateur. Si vous voulez le rafraîchir uniquement après un certain laps de temps, vous pouvez utiliser cette chaîne ISO à la place :

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

Et extraire une partie pour qu'elle ne change qu'après une heure par exemple :

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

J'espère que cela vous aidera

4 votes

Donc en fait, mon implémentation n'a pas fini par fonctionner. Le cache est un problème étrange. Parfois ça marche et parfois non. Oh la beauté des problèmes intermittents. J'ai en fait adapté votre réponse de la manière suivante: templateUrl: './app/shared/menu/menu.html?v=' + Math.random()

0 votes

Je reçois une erreur 404 pour mes templateUrls. Par exemple: GET localhost:8080/app.component.html/?v=0.0.1-alpha 404 (Non trouvé). Une idée du pourquoi?

0 votes

@Rikku121 Non, ce n'est pas le cas. En réalité, c'est sans le / dans l'URL. Je l'ai peut-être ajouté accidentellement quand j'ai posté le commentaire

31voto

Rossco Points 996

Dans chaque modèle html, je rajoute simplement les balises méta suivantes en haut :

À ma connaissance, chaque modèle est indépendant, il n'hérite donc pas des règles de non-mise en cache définies dans le fichier index.html.

5 votes

Nous sommes passés à webpack il y a quelque temps et il se charge de casser le cache de nos applications angular. C'est bien de savoir que votre solution fonctionne aussi. Merci

0 votes

Cela l'a fait pour moi aussi

11voto

NiallMitch14 Points 612

Une combinaison de la réponse de @Jack et de la réponse de @ranierbit devrait faire l'affaire.

Définissez le drapeau ng build pour --output-hashing comme suit:

ng build --output-hashing=all

Ensuite, ajoutez cette classe soit dans un service soit dans votre app.module

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

Ensuite, ajoutez ceci à vos fournisseurs dans votre app.module:

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

Cela devrait prévenir les problèmes de mise en cache sur les sites en direct pour les machines clientes

4voto

Aviko Points 101

J'ai eu un problème similaire avec le fichier index.html étant mis en cache par le navigateur ou plus difficilement par des cdn/proxies intermédiaires (F5 ne vous aidera pas).

J'ai cherché une solution qui vérifie à 100% que le client a la version la plus récente de index.html, heureusement j'ai trouvé cette solution par Henrik Peinar :

https://blog.nodeswat.com/automagic-reload-for-clients-after-deploy-with-angular-4-8440c9fdd96c

La solution résout également le cas où le client reste avec le navigateur ouvert pendant des jours, le client vérifie les mises à jour à intervalles et recharge si une version plus récente est déployée.

La solution est un peu astucieuse mais fonctionne à merveille :

  • utilisez le fait que ng cli -- prod produit des fichiers hachés avec l'un d'eux appelé main.[hash].js
  • créez un fichier version.json contenant ce hachage
  • créez un service Angular VersionCheckService qui vérifie version.json et recharge si nécessaire.
  • Notez qu'un script js s'exécutant après le déploiement crée pour vous à la fois version.json et remplace le hachage dans le service Angular, donc aucun travail manuel n'est nécessaire, mais exécutez post-build.js.

Puisque la solution de Henrik Peinar était pour Angular 4, il y a eu des changements mineurs, je place également les scripts corrigés ici :

VersionCheckService :

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class VersionCheckService {
    // ce sera remplacé par le hachage réel post-build.js
    private currentHash = '{{POST_BUILD_ENTERS_HASH_HERE}}';

    constructor(private http: HttpClient) {}

    /**
     * Vérifie à une fréquence définie la version de l'application frontend
     * @param url
     * @param {number} frequency - en millisecondes, par défaut à 30 minutes
     */
    public initVersionCheck(url, frequency = 1000 * 60 * 30) {
        // vérifie la première fois
        this.checkVersion(url); 

        setInterval(() => {
            this.checkVersion(url);
        }, frequency);
    }

    /**
     * Fait l'appel et vérifie si le hachage a changé ou non
     * @param url
     */
    private checkVersion(url) {
        // horodatage de ces requêtes pour invalider les caches
        this.http.get(url + '?t=' + new Date().getTime())
            .subscribe(
                (response: any) => {
                    const hash = response.hash;
                    const hashChanged = this.hasHashChanged(this.currentHash, hash);

                    // Si nouvelle version, faire quelque chose
                    if (hashChanged) {
                        // ENTREZ VOTRE CODE POUR FAIRE QUELQUE CHOSE EN CAS DE CHANGEMENT DE VERSION
                        // par exemple : location.reload();
                        // ou pour garantir un manque de cdn : window.location.replace(window.location.href + '?rand=' + Math.random());
                    }
                    // enregistrer le nouveau hachage pour ne pas déclencher de nouveau le changement de version
                    // uniquement nécessaire si vous n'avez pas forcé le rafraîchissement
                    this.currentHash = hash;
                },
                (err) => {
                    console.error(err, 'Impossible de récupérer la version');
                }
            );
    }

    /**
     * Vérifie si le hachage a changé.
     * Ce fichier contient le hachage JS, s'il est différent de celui dans le version.json
     * nous sommes en présence d'un changement de version
     * @param currentHash
     * @param newHash
     * @returns {boolean}
     */
    private hasHashChanged(currentHash, newHash) {
        if (!currentHash || currentHash === '{{POST_BUILD_ENTERS_HASH_HERE}}') {
            return false;
        }

        return currentHash !== newHash;
    }
}

changer dans le AppComponent principal :

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    constructor(private versionCheckService: VersionCheckService) {

    }

    ngOnInit() {
        console.log('AppComponent.ngOnInit() environment.versionCheckUrl=' + environment.versionCheckUrl);
        if (environment.versionCheckUrl) {
            this.versionCheckService.initVersionCheck(environment.versionCheckUrl);
        }
    }

}

Le script post-build qui fait de la magie, post-build.js :

const path = require('path');
const fs = require('fs');
const util = require('util');

// obtenir la version de l'application à partir de package.json
const appVersion = require('../package.json').version;

// promisifier les API's de base
const readDir = util.promisify(fs.readdir);
const writeFile = util.promisify(fs.writeFile);
const readFile = util.promisify(fs.readFile);

console.log('\nExécution des tâches post-build');

// notre version.json sera dans le dossier dist
const versionFilePath = path.join(__dirname + '/../dist/version.json');

let mainHash = '';
let mainBundleFile = '';

// RegExp pour trouver main.bundle.js, même s'il n'inclut pas de hachage dans son nom (construction de développement)
let mainBundleRegexp = /^main.?([a-z0-9]*)?.js$/;

// lire les fichiers du dossier dist et trouver celui que nous cherchons
readDir(path.join(__dirname, '../dist/'))
  .then(files => {
    mainBundleFile = files.find(f => mainBundleRegexp.test(f));

    if (mainBundleFile) {
      let matchHash = mainBundleFile.match(mainBundleRegexp);

      // s'il a un hachage dans son nom, le marquer
      if (matchHash.length > 1 && !!matchHash[1]) {
        mainHash = matchHash[1];
      }
    }

    console.log(`Écriture de la version et du hachage dans ${versionFilePath}`);

    // écrire la version actuelle et le hachage dans le fichier version.json
    const src = `{"version": "${appVersion}", "hash": "${mainHash}"}`;
    return writeFile(versionFilePath, src);
  }).then(() => {
    // fichier du bundle principal non trouvé, construction de développement ?
    if (!mainBundleFile) {
      return;
    }

    console.log(`Remplacement du hachage dans le ${mainBundleFile}`);

    // remplacer le marqueur de hachage dans notre fichier main.js pour que le code connaisse son hachage actuel
    const mainFilepath = path.join(__dirname, '../dist/', mainBundleFile);
    return readFile(mainFilepath, 'utf8')
      .then(mainFileData => {
        const replacedFile = mainFileData.replace('{{POST_BUILD_ENTERS_HASH_HERE}}', mainHash);
        return writeFile(mainFilepath, replacedFile);
      });
  }).catch(err => {
    console.log('Erreur avec la construction post :', err);
  });

placez simplement le script dans le nouveau dossier build exécutez le script en utilisant node ./build/post-build.js après avoir construit le dossier dist en utilisant ng build --prod

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X