So, you're working on your Angular + MVC/Web API project, when suddenly you get irritated with your browser caching your views for the last time. That's when it hits you. This is going to happen in production. You consult the Network tab of your browser's dev tools, and see all of the extra connections that are occurring because you made nice modular directives with separate files for your templates. Now, take a breath as you find the solution to both problems right here.

The trick is to create a class that inherits from Bundle , the base class of ScriptBundle and StyleBundle . There are a couple of constructors worth implementing, one without a CDN path and one with a CDN path. The latter will call the base constructor and pass down an IBundleTransform . You will need a custom implementation IBundleTransform class as well, which is where the magic really happens. IBundleTransform has one method to implement: Process() , which takes in the bundle details and produces a response. In this case, our Process() implementation will iterate through the templates, convert them to strings, and insert them into a string containing a javascript IIFE that will populate the $templateCache. Take note that we must properly escape single quotes and remove newlines. With this complete, we just implement our bundles in BundleConfig.cs. Just give the bundle a name and the name of the Angular module you are adding templates for, and populate the bundle with HTML files. Now, when your site is in production, it will benefit from all of the cache-busting, single network call goodness of your JS and CSS bundles. Oh, and when you build the ScriptBundle for the rest of your Angular app, be sure to put your module definition file first! See examples below:

Bundle and IBundleTransform

using System; using System.IO; using System.Linq; using System.Text; using System.Web.Optimization; namespace Azurelogic.Utilities { public class AngularTemplateBundle : Bundle { public AngularTemplateBundle(string virtualPath, string moduleName) : this(virtualPath, (string)null, moduleName) { } public AngularTemplateBundle(string virtualPath, string cdnPath, string moduleName) : base(virtualPath, cdnPath, new AngularTemplateBundleTransform(moduleName)) { } } public class AngularTemplateBundleTransform : IBundleTransform { private readonly string _moduleName; public AngularTemplateBundleTransform(string moduleName) { _moduleName = moduleName; } public void Process(BundleContext context, BundleResponse response) { var builder = new StringBuilder(); builder.AppendFormat("(function(){{angular.module('{0}')", _moduleName); builder.AppendLine(".run(['$templateCache', function($templateCache) {"); foreach (var file in response.Files.Select(bundleFile => bundleFile.VirtualFile).Where(file => !file.IsDirectory)) { using (var reader = new StreamReader(file.Open())) { var ngTemplate = string.Format("$templateCache.put('{0}', '{1}');", file.VirtualPath, reader.ReadToEnd().Trim().Replace("'", "\\'").Replace("\r\n", " ").Replace("\n", " ")); builder.AppendLine(ngTemplate); } } builder.AppendLine("}]);})();"); response.Content = builder.ToString(); response.ContentType = "text/javascript"; } } }


using System.Web; using System.Web.Optimization; using Azurelogic.Utilities; namespace Azurelogic { public class BundleConfig { private const string VirtualPath = "~/Content/Azurelogic/"; private const string ModuleVirtualPath = VirtualPath + "azurelogic.module.js"; public static void RegisterBundles(BundleCollection bundles) { //... bundles.Add(new ScriptBundle("~/bundles/azurelogic") .Include(ModuleVirtualPath) .IncludeDirectory(VirtualPath, "*.js", true)); bundles.Add(new AngularTemplateBundle("~/bundles/azurelogicTemplates", "azurelogic") .IncludeDirectory(VirtualPath, "*.html", true)); //... } } } Update

Everything above will work fine as is, but there is one catch. In debug mode, you will get JS syntax errors in your console, as it attempts to execute the HTML files as scripts. Since this will fail, Angular will fall back to retrieving your templates directly from the server. If you want to keep your console clean of these errors, you need to add type="text/ng-template" to your script tags when in debug. What you're really after is when optimizations are turned on, so you can hang the condition on BundleTable.EnableOptimizations . See this example:


@* down at the bottom with the other scripts *@ @Scripts.Render("~/bundles/azurelogic") @if (BundleTable.EnableOptimizations) { @Scripts.Render("~/bundles/azurelogicTemplates") } else { @Scripts.RenderFormat("<script type=\"text/ng-template\" src=\"{0}\"></script>", "~/bundles/azurelogicTemplates") } References

These posts were instrumental in helping me put this whole picture together:

Bundling and Minifying an AngularJS Application with ASP.NET MVC Bundling AngularJS Templates with ASP.NET MVC ASP.NET BUNDLING OF ANGULAR TEMPLATES

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

本文标题:Optimizing Angular $templateCache preloading with custom bundling in ASP.NET MVC

技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(17)