javascriptangularjsgrailsbundling-and-minificationangular-templatecache

Angular $templateCache 404 error for relative templateUrls after bundling


I am using Grails 3 with the AngularJS profile together with the Angular Toastr plugin.
When I run the application in development mode, everything works perfectly but when I bundle the app (without minification), the templates from the plugin can't be loaded anymore and I get the following error:

Error: [$compile:tpload] Failed to load template: directives/toast/toast.html (HTTP status: 404 Not Found)

I checked the bundled code and found the lines:

angular.module('toastr')
    .constant('toastrConfig', {
      allowHtml: false,
      autoDismiss: false,
      closeButton: false,
      closeHtml: '<button>&times;</button>',
      containerId: 'toast-container',
      extendedTimeOut: 1000,
      iconClasses: {
        error: 'toast-error',
        info: 'toast-info',
        success: 'toast-success',
        warning: 'toast-warning'
      },
      maxOpened: 0,
      messageClass: 'toast-message',
      newestOnTop: true,
      onHidden: null,
      onShown: null,
      onTap: null,
      positionClass: 'toast-top-right',
      preventDuplicates: false,
      preventOpenDuplicates: false,
      progressBar: false,
      tapToDismiss: true,
      target: 'body',
      templates: {
        toast: 'directives/toast/toast.html',
        progressbar: 'directives/progressbar/progressbar.html'
      },
      timeOut: 5000,
      titleClass: 'toast-title',
      toastClass: 'toast'
    });

and

angular.module("toastr").run(["$templateCache", function($templateCache) {$templateCache.put("directives/progressbar/progressbar.html","<div class=\"toast-progress\"></div>\n");
$templateCache.put("directives/toast/toast.html","<div class=\"{{toastClass}} {{toastType}}\" ng-click=\"tapToast()\">\n  <div ng-switch on=\"allowHtml\">\n    <div ng-switch-default ng-if=\"title\" class=\"{{titleClass}}\" aria-label=\"{{title}}\">{{title}}</div>\n    <div ng-switch-default class=\"{{messageClass}}\" aria-label=\"{{message}}\">{{message}}</div>\n    <div ng-switch-when=\"true\" ng-if=\"title\" class=\"{{titleClass}}\" ng-bind-html=\"title\"></div>\n    <div ng-switch-when=\"true\" class=\"{{messageClass}}\" ng-bind-html=\"message\"></div>\n  </div>\n  <progress-bar ng-if=\"progressBar\"></progress-bar>\n</div>\n");}]);

so it seems that the templates are initialized correctly in the template cache.

I tried injecting the $templateCache in a controller and called $templateCache.get("directives/toast/toast.html") and this returns me the correct template.

What could be the reason that the template is not loaded correctly when bundled, although I can access it with $templateCache.get(...)?
Is there anything I am missing about the correct usage of the $templateCache?

PS: I noted the same problem for angular-bootstrap templates

Edit I found out, that everything works, when I use absolute templateUrls, so apparently, I don't fully understand, how the relative templateUrls work.
When the app is bundled, all the JS code is concatenated to a single file with a different path, which seems to break the loading via the $templateCache. Now, making all the templateUrls absolute would be a solution, but I can not do that for plugins, which use a relative templateUrl, without changing their code.

So, can anybody explain to me what is actually happening here and how I could fix this, without touching the plugin code?


Solution

  • I found out, that when generating the Grails app for Angular, it automatically includes the following lines in the index.gsp:

    <script type="text/javascript">
        window.contextPath = "${request.contextPath}";
    </script>
    

    This sets the window.contextPath when the app is bundled for production which breaks the Angular $templateCache.

    In other words: Set window.contextPath = "" or the template resolution from the $templateCache will fail.