AngularJS 如何在开发机器上禁用部分缓存

212

我在使用AngularJS缓存部分内容时遇到了问题。

我的HTML页面中有以下代码:

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

我的部分视图文件在哪里加载。

当我更改部分视图文件中的HTML代码时,浏览器仍会加载旧数据。

有什么解决方法吗?


4
简要说明:我遇到了一个问题,与我的 Flask 应用程序返回的缓存控制头有关。为了解决这个问题,我在 flask_app.py 中添加了 app.config.update(SEND_FILE_MAX_AGE_DEFAULT=0)。(我想其他网络服务器也存在类似的情况)。 - gatoatigrado
5
如果您正在使用Chrome浏览器,只需执行“Ctrl + Shift + R”(即硬刷新),无论使用何种缓存机制,Chrome都会忽略它并重新获取所有脚本、样式表等。 - snajahi
4
在Chrome浏览器中,ctrl+shift+R对我不起作用,但在开发者工具的“网络”选项卡中,点击“禁用缓存”可以完美解决。对我来说,这是一个客户端问题,不应该像下面的建议一样在服务器上使用hack来解决;它应该在存在“问题”的客户端上进行修复。如果您在服务器上修复了它,并忘记了撤销,生产可能会受到不利影响。 - Ant Kutschera
9
按下ctrl+shift+R可以绕过缓存普通请求。从Angular发出的用于ng-include|ng-view|templateUrl的ajax请求不会被这个快捷方式处理。 - André Werlang
2
你不能要求所有终端用户在访问网站时都按Ctrl+Shift+R,那么对于非开发案例,这个问题的答案是什么?“对我来说,这是一个客户端问题,不应该像下面的许多建议一样在服务器上使用黑客解决。” - 我不同意,你无法在Web环境中控制客户端,因此生产的修复必须是应用程序驱动的。出于这个原因,我接受了:$rootScope.$on('$viewContentLoaded',function(){ $templateCache.removeAll(); }); - Robert Christian
12个回答

200

对于开发,您也可以禁用浏览器缓存 - 在Chrome Dev Tools中,在右下角点击齿轮并选中选项

在DevTools打开时禁用缓存

更新:在Firefox中,调试器->设置->高级部分中也有相同的选项(适用于版本33)。

更新2:尽管此选项出现在Firefox中,但有些人报告说它不起作用。建议使用Firebug并按照hadaytullah的答案操作。


7
这应该是被接受的答案,因为它不需要修改代码,并且更符合原帖请求的要点。当然,你可能希望在生产应用中缓存请求,因此执行上述建议虽然出于善意,但如果代码保留在生产应用中可能会出现问题。 - Aaron Wagner
1
有没有其他浏览器(如Firefox)的建议? - Lereveme
5
这在Firefox中不起作用。即使禁用缓存并打开工具箱,模板仍然被缓存。 - Patrick J Collins
4
缓存对生产环境有影响吗?如果我向服务器推送新的网页文件,是否会防止生产客户端后续请求加载已经缓存的旧版本? - Northstrider
@PatrickJCollins 在火狐浏览器中也遇到了同样的问题。我通过进入菜单:历史记录 > 清除最近的历史记录,选择1小时并只勾选“缓存”来解决了这个问题。当然,这不是一个好的解决方案。 - AceRymond
显示剩余2条评论

112
根据 @Valentyn 的回答,这里提供一种方法,在 ng-view 内容更改时自动清除缓存:
myApp.run(function($rootScope, $templateCache) {
   $rootScope.$on('$viewContentLoaded', function() {
      $templateCache.removeAll();
   });
});

@user252690 可能还需要确保HTML模板没有与缓存标头一起发送。请参见此处此处以获取可能的解决方案。 - Chris Foster
30
一个小提示:清空$templateCache可能会产生意料之外的后果。例如,UI Bootstrap在初始化期间直接向$templateCache添加默认偏文件,然后期望它们仍然存在。 - Strille
@Strille 我正在尝试使用angular-ui-bootstrap模态框,但弹出窗口不可见。这是因为$templateCache.removeAll()的缘故吗?有什么解决方法吗? - Mukun
4
@Mukun:在我看来,没有简单的解决方法,除了不使用removeAll(),而是使用remove()来删除您需要清除的键。您需要一些记账来知道要删除哪些键。 - Strille
有没有办法只清除特定的UI视图缓存? - Gayan
这在 Angular 2 中会是什么样子? - Ondra Žižka

38

正如其他答案中所提到的,这里这里,可以通过使用以下方式清除缓存:

$templateCache.removeAll();

然而,如gatoatigrado评论中所建议的,这只有在HTML模板没有使用任何缓存头部信息时才能起作用。

因此,以下方法适用于我:

在Angular中:

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

你可能正在以各种方式添加缓存标头,但以下是对我有效的几种解决方案。

如果使用 IIS,请将以下内容添加到您的 web.config 文件中:

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

如果使用Nginx,你可以将以下内容添加到你的配置文件中:

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

编辑

我刚刚意识到问题提到了dev机器,但希望这仍然能帮助某人...


2
是的,尽管这并没有直接回答原问题,但事实上它帮助我解决了一个实时网站上的缓存问题。 - Andre

31
如果您在谈论用于缓存模板而无需重新加载整个页面的缓存,则可以通过类似以下方式来清空它:

如果您正在谈论用于缓存模板而不需要重新加载整个页面的缓存,那么您可以通过类似以下方式来清空它:

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

在标记中:

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

按下此按钮以清除缓存。


22

Firefox(33.1.1)使用Firebug(22.0.6)的解决方案

  1. 工具 > Web工具 > Firebug > 打开Firebug。
  2. 在Firebug视图中转到“ Net”视图。
  3. “Net”(视图标题)旁边将出现一个下拉菜单符号。
  4. 从下拉菜单中选择“禁用浏览器缓存”。

在Mac上尝试使用Firebug(2.0.11)和Firefox(38.0.1),但这并没有起作用。 - paullb

19

这个片段帮助我摆脱了模板缓存

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

以下代码段的详细信息可以在此链接中找到: http://oncodesign.io/2014/02/19/safely-prevent-template-caching-in-angularjs/


挑剔一下,但是什么时候当前对象不是一个对象呢?我不确定它是否不存在,但是如果(!current) { return; }。 - Nate-Wilkins
这将打败Angular基于路由的模板缓存,但不会影响ng-include的局部视图。 - bradw2k

17

由于其他解决方案均无法解决我的问题(由于 angular-bootstrap 模板依赖等原因造成错误),因此我在此发布帖子以涵盖所有可能性。

在您开发/调试特定模板时,可以通过在路径中包含时间戳来确保其始终刷新,像这样:

       $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();
            };
          }
        });

请注意templateUrl变量中的最后一个?nd=' + Date.now()


1
稍后您可以设置 .value('DEBUG', true) 来启用或禁用该行。 - Diosney
1
我的解决方案是在我的主模块的初始化阶段中使用以下代码 .run(function($rootScope) { $rootScope.DEBUG = true; ...,然后在指令中注入$rootScope,如下所示:.directive('filter', ['$rootScope', function($rootScope)...,并在返回的对象属性中添加:templateUrl: '/app/components/filter/filter-template.html' + ($rootScope.DEBUG ? '?n=' + Date.now() : '')。也许您可以详细说明一下您的.value('DEBUG', true)方法?已点赞! - JackLeEmmerdeur
使用 .value('DEBUG', true) 的方式与 $rootScope 相同,但不会使其混乱 :) 你可以稍后将 DEBUG 注入到控制器中,并像普通服务一样查询。 - Diosney
你能否在你的答案中扩展源代码以包括.value(...)这个东西,如果不太复杂的话?我猜背后的概念是我不知道的Angular最佳实践。 - JackLeEmmerdeur
1
这个解决方案在使用Ionic时非常有用。它将使热重载再次变得有用,从而节省了我的大量时间。非常感谢! - ajuser
显示剩余2条评论

11

正如其他人所说,为了开发目的完全禁用缓存可以很容易地在不更改代码的情况下实现:使用浏览器设置或插件。在开发之外,要打败基于路由的Angular模板缓存,请在$ routeChangeStart(或对于UI Router的$ stateChangeStart)期间从缓存中删除模板URL,如Shayan所示。但是,这并不影响通过ng-include加载的模板的缓存,因为这些模板不通过路由加载。

我想能够快速修复生产中包括通过ng-include加载的任何模板,并使用户能够快速在其浏览器中接收到热修复,而无需重新加载整个页面。我也不关心针对模板的HTTP缓存。解决方案是拦截应用程序发出的每个HTTP请求,忽略那些不是用于我的应用程序的 .html 模板的请求,然后向模板的URL添加一个每分钟更改一次的参数。请注意,路径检查特定于您的应用程序模板的路径。要获得不同的间隔,请更改参数数学,或完全删除%以获取没有缓存的结果。

// 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;
    }

我对你的解决方案很感兴趣 - 你会一直保留这段代码吗,还是在进行更改后一段时间内?原因是我担心性能问题。此外,你提到副作用会破坏HTTP。你的意思是这是一种好的副作用方式,也就是说这是你为了进行“热修复”而打算的吗?谢谢。 - jamie
1
我仍然留下这段代码,尽管我们将缓存破坏的时间缩短到了10分钟。因此,在使用的每10分钟内,用户将重新加载新的HTML模板。 对于我的业务应用程序来说,这是一个可以接受的成本,以获得热补丁模板的能力,但显然对于某些类型的应用程序来说,它会影响性能太多。...很不幸,HTTP缓存也被打败了,但我没有看到一种可以智能地击败Angular模板缓存而不打败ETag等的方法。在我看来,Angular模板缓存的可配置性还不够。 - bradw2k
1
+1 我也有同样的想法,但我只拦截本地主机。您可以在此处查看我的拦截器实现: http://overengineer.net/preventing-angularjs-from-caching-your-templates-and-other-files - joshcomley
如果您只需要在本地主机上解决缓存问题,为什么不使用一个可以打败所有缓存的浏览器插件呢? - bradw2k
@bradw2k 这样我完全掌控了缓存内容,这对开发非常有用。我还在所有浏览器中进行测试,不是所有浏览器都有满足我的需求的扩展程序。我还可以在开发站点上设置指示器,告诉我缓存是否已禁用,因为有时我只想在浏览器中暂时禁用并测试。 - joshcomley

8
如果您正在使用UI router,那么可以使用装饰器并更新$templateFactory服务,并将查询字符串参数附加到templateUrl中,浏览器将始终从服务器加载新的模板。
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]);

我相信您可以通过在$routeProvider中装饰“when”方法来实现相同的结果。


一个更好的选择是使用像gulp-angular-templatecache这样的插件来在$templateCache中注册angular js模板。这应该与gulp-rev一起使用。每当模板发生变化时,都会创建一个带有不同修订号的新JavaScript文件,并且缓存永远不会成为问题。 - Aman Mahajan

3
我发现HTTP拦截器方法非常好用,可以提供额外的灵活性和控制。此外,您可以使用发布哈希作为buster变量来为每次生产发布清除缓存。
以下是使用Date的dev cachebusting方法的代码样式。
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');
}]);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接