AngularJS désactive la mise en cache partielle sur la machine de développement

210

J'ai un problème avec la mise en cache des partiels dans AngularJS.

Dans ma page HTML, j'ai:

<body>
 <div ng-view></div>
<body>

où mes partiels sont chargés.

Lorsque je modifie le code HTML dans mon partiel, le navigateur charge toujours les anciennes données.

Y a-t-il une solution de contournement?

Mennion
la source
4
Juste une note rapide: j'ai eu un problème avec cela qui était plus lié aux en-têtes de contrôle du cache que mon application Flask renvoyait. J'ai contourné le problème en ajoutant app.config.update(SEND_FILE_MAX_AGE_DEFAULT=0)à mon flask_app.py. (J'imagine que des choses similaires existent pour d'autres serveurs Web).
gatoatigrado
4
Si vous utilisez Chrome, faites juste un Ctrl+Shift+R(c'est-à-dire Hard Reload) et quel que soit le mécanisme de mise en cache utilisé, Chrome l'ignorera et récupérera tous les scripts, feuilles de style, etc.
snajahi
4
ctrl + shift + R ne fonctionne pas pour moi dans Chrome, mais sur l'onglet "réseau" des outils de développement, cliquer sur "désactiver le cache" fonctionne parfaitement. Pour moi, c'est un problème côté client qui ne devrait pas être résolu en utilisant des hacks sur le serveur comme la plupart des suggestions ci-dessous; il doit être corrigé sur le client où le "problème" existe. Si vous le corrigez sur le serveur et oubliez de le résoudre, la production pourrait en être affectée.
Ant Kutschera
8
ctrl + shift + R contourne le cache pour les requêtes normales. ajax fait des demandes angulaires pour ng-include| ng-view| templateUrlne sont pas traités par ce raccourci
André Werlang
2
Vous ne pouvez pas demander à tous les utilisateurs finaux Ctrl + Shift + R lors de la visite du site, alors quelle est la réponse à cette question pour le cas de non-développement? "Pour moi, c'est un problème côté client qui ne devrait pas être résolu en utilisant des hacks sur le serveur comme la plupart des suggestions ci-dessous" - Je ne suis pas d'accord, vous ne pouvez pas contrôler les clients dans un environnement Web, donc le correctif pour la production doit être axé sur l'application. Pour cette raison, j'ai accepté: $ rootScope. $ On ('$ viewContentLoaded', function () {$ templateCache.removeAll ();});
Robert Christian

Réponses:

200

Pour le développement, vous pouvez également désactiver le cache du navigateur - Dans Chrome Dev Tools en bas à droite, cliquez sur l'engrenage et cochez l'option

Désactiver le cache (lorsque DevTools est ouvert)

Mise à jour: dans Firefox, il y a la même option dans Debugger -> Paramètres -> Section avancée (vérifié pour la version 33)

Mise à jour 2: Bien que cette option apparaisse dans Firefox, certains rapports indiquent qu'elle ne fonctionne pas. Je suggère d'utiliser Firebug et de suivre la réponse de hadaytullah.

LukeSolar
la source
7
Cela devrait être la réponse acceptée car elle ne nécessite pas de changement de code et correspond davantage à la demande du PO. Bien sûr, vous voudriez qu'une application de production mette en cache les demandes, donc faire ce que les gens ci-dessus ont suggéré, bien que bien intentionné, pourrait s'avérer problématique si le code est laissé dans une application de production.
Aaron Wagner
Des suggestions pour les autres navigateurs comme Firefox?
Lereveme
Dans Firefox: Débogueur> Paramètres (l'engrenage), il y a la même option.
LukeSolar
5
Cela ne fonctionne pas dans Firefox. Même lorsque le cache est désactivé et que la boîte à outils est ouverte, les modèles sont toujours mis en cache.
Patrick J Collins
4
La mise en cache affecte-t-elle également la production? Que se passe-t-il si je transmets de nouveaux fichiers Web au serveur, qu'est-ce qui empêche les demandes ultérieures des clients de production de charger une version mise en cache pré-publiée?
meffect
111

En s'appuyant un peu sur la réponse de @ Valentyn, voici une façon de toujours vider automatiquement le cache chaque fois que le contenu ng-view change:

myApp.run(function($rootScope, $templateCache) {
   $rootScope.$on('$viewContentLoaded', function() {
      $templateCache.removeAll();
   });
});
Mark Rajcok
la source
@ user252690 Vous devez probablement également vous assurer que le modèle html n'a pas été envoyé avec des en-têtes de cache. Voir ici et ici pour les correctifs possibles
Chris Foster
30
Un petit avertissement: vider le $ templateCache peut avoir des conséquences inattendues. Par exemple, UI Bootstrap ajoute des partiels par défaut directement au $ templateCache lors de l'initialisation et s'attend plus tard à ce qu'ils soient là.
Strille
@Strille J'essayais d'utiliser le modal angular-ui-bootstrap. La fenêtre contextuelle n'était pas visible. parce que $ templateCache.removeAll (); une solution?
Mukun
4
@Mukun: Il n'y a pas de solution facile à la façon dont je le vois, à part ne pas utiliser removeAll (), mais à la place, utilisez remove () pour supprimer les clés que vous devez effacer. Vous auriez besoin d'une sorte de comptabilité pour savoir quelles clés supprimer.
Strille
existe-t-il des moyens de vider le cache uniquement pour le cache de vue d'interface utilisateur spécifique.
Gayan
37

Comme mentionné dans les autres réponses, ici et ici , le cache peut être effacé en utilisant:

$templateCache.removeAll();

Cependant, comme suggéré par gatoatigrado dans le commentaire , cela ne semble fonctionner que si le modèle html a été servi sans en-têtes de cache.

Donc ça marche pour moi:

En angulaire:

app.run(['$templateCache', function ( $templateCache ) {
    $templateCache.removeAll(); }]);

Vous pouvez ajouter des en-têtes de cache de différentes manières, mais voici quelques solutions qui fonctionnent pour moi.

Si vous utilisez IIS, ajoutez ceci à votre web.config:

<location path="scripts/app/views">
  <system.webServer>
    <staticContent>
      <clientCache cacheControlMode="DisableCache" />
    </staticContent>
  </system.webServer>
</location>

Si vous utilisez Nginx, vous pouvez l'ajouter à votre configuration:

location ^~ /scripts/app/views/ {
    expires -1;   
}

Éditer

Je viens de réaliser que la question mentionnait la devmachine mais j'espère que cela peut encore aider quelqu'un ...

Chris Foster
la source
2
oui, même si cela ne répond pas directement à la question d'origine, cela m'a en fait aidé à résoudre le problème de mise en cache sur un site Web en direct.
Andre
31

Si vous parlez de cache qui a été utilisé pour la mise en cache de modèles sans recharger la page entière, vous pouvez le vider par quelque chose comme:

.controller('mainCtrl', function($scope, $templateCache) {
  $scope.clearCache = function() { 
    $templateCache.removeAll();
  }
});

Et en balisage:

<button ng-click='clearCache()'>Clear cache</button>

Et appuyez sur ce bouton pour vider le cache.

Valentyn Shybanov
la source
22

Solution pour Firefox (33.1.1) utilisant Firebug (22.0.6)

  1. Outils> Outils Web> Firebug> Ouvrir Firebug.
  2. Dans les vues Firebug, allez dans la vue "Net".
  3. Un symbole de menu déroulant apparaîtra à côté de "Net" (titre de la vue).
  4. Sélectionnez "Désactiver le cache du navigateur" dans le menu déroulant.
hadaytullah
la source
J'ai essayé Firebug (2.0.11) et Firefox (38.0.1) sur mac mais cela n'a pas fonctionné.
paullb
19

Cet extrait m'a aidé à me débarrasser de la mise en cache des modèles

app.run(function($rootScope, $templateCache) {
    $rootScope.$on('$routeChangeStart', function(event, next, current) {
        if (typeof(current) !== 'undefined'){
            $templateCache.remove(current.templateUrl);
        }
    });
});

Les détails de l'extrait suivant peuvent être trouvés sur ce lien: http://oncodesign.io/2014/02/19/safely-prevent-template-caching-in-angularjs/

Code Prank
la source
nitpick mais quand le courant n'est-il jamais un objet? Je ne suis pas sûr qu'il ne soit jamais là non plus, maisif (!current) { return; }
Nate-Wilkins
Cela vainc la mise en cache par Angular des modèles basés sur l'itinéraire, mais pas les partiels ng-include.
bradw2k
16

Je poste ceci juste pour couvrir toutes les possibilités car aucune des autres solutions n'a fonctionné pour moi (ils ont jeté des erreurs en raison des dépendances du modèle angular-bootstrap, entre autres).

Pendant que vous développez / déboguez un modèle spécifique, vous pouvez vous assurer qu'il est toujours actualisé en incluant un horodatage dans le chemin, comme ceci:

       $modal.open({
          // TODO: Only while dev/debug. Remove later.
          templateUrl: 'core/admin/organizations/modal-selector/modal-selector.html?nd=' + Date.now(),
          controller : function ($scope, $modalInstance) {
            $scope.ok = function () {
              $modalInstance.close();
            };
          }
        });

Notez la finale ?nd=' + Date.now()dans la templateUrlvariable.

diosney
la source
1
Plus tard, vous pouvez définir un .value('DEBUG', true)pour activer cette ligne ou non.
diosney
1
Ma solution a été d'utiliser les éléments suivants dans la phase d'initialisation de mon module principal sous .run(function($rootScope) { $rootScope.DEBUG = true; ...et à l' intérieur de la directive Injecter la rootScope de $ comme .directive('filter', ['$rootScope', function($rootScope)...et dans l'objet bien retourné: templateUrl: '/app/components/filter/filter-template.html' + ($rootScope.DEBUG ? '?n=' + Date.now() : ''). Peut-être pourriez-vous élaborer votre approche .value ('DEBUG', true)? A voté!
JackLeEmmerdeur
L'utilisation de .value('DEBUG', trueest la même que pour $rootScope, mais sans l'encombrer :) Vous pourrez ensuite injecter DEBUGdans le contrôleur et interroger en tant que service normal.
diosney
Pourriez-vous peut-être étendre le code source dans votre réponse pour inclure le .value(...)truc, s'il n'est pas trop sophistiqué? Je suppose que le concept derrière est une meilleure pratique angulaire qui m'est inconnue.
JackLeEmmerdeur
1
Cette solution est très utile lorsque vous travaillez avec Ionic. Ça va me faire gagner beaucoup de temps car cela rend la charge hépatique à nouveau utile. Merci beaucoup!
ajuser
11

Comme d'autres l'ont dit, la suppression complète de la mise en cache à des fins de développement peut se faire facilement sans changer de code: utilisez un paramètre de navigateur ou un plugin. En dehors de dev, pour vaincre la mise en cache des modèles angulaires des modèles basés sur l'itinéraire, supprimez l'URL du modèle du cache pendant $ routeChangeStart (ou $ stateChangeStart, pour le routeur d'interface utilisateur), comme l'a montré Shayan. Cependant, cela n'affecte PAS la mise en cache des modèles chargés par ng-include, car ces modèles ne sont pas chargés via le routeur.

Je voulais pouvoir corriger n'importe quel modèle, y compris ceux chargés par ng-include, en production et que les utilisateurs reçoivent le correctif dans leur navigateur rapidement, sans avoir à recharger la page entière. Je ne suis pas non plus préoccupé par la suppression de la mise en cache HTTP pour les modèles. La solution consiste à intercepter toutes les demandes HTTP effectuées par l'application, à ignorer celles qui ne sont pas destinées aux modèles .html de mon application, puis à ajouter un paramètre à l'URL du modèle qui change chaque minute. Notez que la vérification du chemin est spécifique au chemin des modèles de votre application. Pour obtenir un intervalle différent, modifiez le calcul du paramètre ou supprimez complètement le% pour ne pas mettre en cache.

// this defeats Angular's $templateCache on a 1-minute interval
// as a side-effect it also defeats HTTP (browser) caching
angular.module('myApp').config(function($httpProvider, ...) {
    $httpProvider.interceptors.push(function() {
        return {
            'request': function(config) {
                config.url = getTimeVersionedUrl(config.url);
                return config;
            }
        };
    });

    function getTimeVersionedUrl(url) {
        // only do for html templates of this app
        // NOTE: the path to test for is app dependent!
        if (!url || url.indexOf('a/app/') < 0 || url.indexOf('.html') < 0) return url;
        // create a URL param that changes every minute
        // and add it intelligently to the template's previous url
        var param = 'v=' + ~~(Date.now() / 60000) % 10000; // 4 unique digits every minute
        if (url.indexOf('?') > 0) {
            if (url.indexOf('v=') > 0) return url.replace(/v=[0-9](4)/, param);
            return url + '&' + param;
        }
        return url + '?' + param;
    }
bradw2k
la source
Je suis intéressé par votre solution - conservez-vous ce code pour de bon - ou pendant un certain temps après avoir effectué un changement? Raison - serait préoccupé par la performance. Vous mentionnez également les effets secondaires qui défait HTTP. Vous voulez dire que c'est un bon moyen d'effet secondaire correct - ce qui signifie que c'est ce que vous vouliez pour avoir un «correctif»? Thx
jamie
1
Je laisse ce code pour de bon, bien que nous ayons rappelé la durée de contournement du cache à 10 minutes. Ainsi, toutes les 10 minutes d'utilisation, un utilisateur rechargera de nouveaux modèles html. Pour mon application biz, c'est un coût acceptable pour gagner la possibilité de patcher à chaud des modèles, mais évidemment cela nuirait trop aux performances pour certains types d'applications. ... Il est malheureux que la mise en cache HTTP soit également vaincue, mais je ne vois pas de moyen de vaincre intelligemment la mise en cache des modèles angulaires sans vaincre les Etag, etc. également. La mise en cache des modèles angulaires IMO n'est tout simplement pas suffisamment configurable.
bradw2k
1
+1 J'ai eu la même idée, mais je n'intercepte que pour localhost. Vous pouvez voir mon implémentation de l'intercepteur ici: overengineer.net/…
joshcomley
@joshcomley Si vous avez seulement besoin de vaincre la mise en cache sur localhost, pourquoi ne pas utiliser un plugin de navigateur qui vainc toute la mise en cache?
bradw2k
@ bradw2k de cette façon, je contrôle totalement ce qui est ou n'est pas mis en cache, ce qui peut être utile pour le développement. Je teste également dans tous les navigateurs, et tous les navigateurs n'ont pas d'extensions qui font ce dont j'ai besoin. Je peux également avoir un indicateur sur le site en développement me disant si la mise en cache est désactivée ou non, car parfois je ne souhaite désactiver et tester les navigateurs que pendant un certain temps.
joshcomley
8

Si vous utilisez un routeur d'interface utilisateur, vous pouvez utiliser un décorateur et mettre à jour le service $ templateFactory et ajouter un paramètre de chaîne de requête à templateUrl, et le navigateur chargera toujours le nouveau modèle à partir du serveur.

function configureTemplateFactory($provide) {
    // Set a suffix outside the decorator function 
    var cacheBust = Date.now().toString();

    function templateFactoryDecorator($delegate) {
        var fromUrl = angular.bind($delegate, $delegate.fromUrl);
        $delegate.fromUrl = function (url, params) {
            if (url !== null && angular.isDefined(url) && angular.isString(url)) {
                url += (url.indexOf("?") === -1 ? "?" : "&");
                url += "v=" + cacheBust;
            }

            return fromUrl(url, params);
        };

        return $delegate;
    }

    $provide.decorator('$templateFactory', ['$delegate', templateFactoryDecorator]);
}

app.config(['$provide', configureTemplateFactory]);

Je suis sûr que vous pouvez obtenir le même résultat en décorant la méthode "when" dans $ routeProvider.

Aman Mahajan
la source
Une bien meilleure alternative consiste à utiliser un plugin comme gulp-angular-templatecache pour enregistrer des modèles js angulaires dans $ templateCache. Cela devrait être utilisé avec gulp-rev. Chaque fois qu'un modèle change, un nouveau fichier JavaScript avec un numéro de révision différent sera créé et la mise en cache ne sera jamais un problème.
Aman Mahajan
3

J'ai trouvé que la méthode d'intercepteur HTTP fonctionne très bien et permet une flexibilité et un contrôle supplémentaires. De plus, vous pouvez mettre en cache cache pour chaque version de production en utilisant un hachage de version comme variable buster.

Voici à quoi ressemble la méthode dev cachebusting Date.

app.factory('cachebustInjector', function(conf) {   
    var cachebustInjector = {
        request: function(config) {    
            // new timestamp will be appended to each new partial .html request to prevent caching in a dev environment               
            var buster = new Date().getTime();

            if (config.url.indexOf('static/angular_templates') > -1) {
                config.url += ['?v=', buster].join('');
            }
            return config;
        }
    };
    return cachebustInjector;
});

app.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('cachebustInjector');
}]);
cpreid
la source
1

Voici une autre option dans Chrome.

Appuyez sur F12pour ouvrir les outils de développement. Ensuite, Ressources > Stockage du cache > Actualiser les caches .

entrez la description de l'image ici

J'aime cette option car je n'ai pas à désactiver le cache comme dans les autres réponses.

Jess
la source
Dans Chrome v. 54.0.2840.99 en novembre 2016, j'ai trouvé cela sur un onglet appelé Application.rather plutôt que Resources.
StackOverflowUser
0

Il n'y a pas de solution pour empêcher la mise en cache du navigateur / proxy car vous ne pouvez pas avoir le contrôle dessus.

L'autre façon de forcer un nouveau contenu à vos utilisateurs pour renommer le fichier HTML! Exactement comme https://www.npmjs.com/package/grunt-filerev le fait pour les actifs.

Thomas Decaux
la source