0

I'm trying to create a directive to load a custom template, but if the custom doesn't exists, load the default template.

Here is my code:

HTML:

<my-include src="path/to/template.html"></my-include>

Directive:

angular.module('app')

    .directive("myInclude", function ($compile) {
        return {
            restrict: 'CAE',
            replace: true,
            scope: {
                src: '@',
            },
            link: function (scope, iElement, iAttrs, controller) {
                scope.$on("$includeContentError", function (event, args) {

                    scope.src = args.replace('/custom', '').replace("'", '');
                });
                scope.$on("$includeContentLoaded", function (event, args) {

                    scope.src = args.replace("'", '');
                });
            },
            template: '<div class="include-container" ng-include="src"></div>'
        };
    })
;

The problem I have is that... my template is not showing... When I debug it, it goes to the directive and replace the src. But the html I get is the following :

<div class="include-container ng-scope" ng-include="src" src="path/to/template.html"><div class="main-information-content ng-scope">
</div>

Any idea how I could solve this issue ? I'm guessing it's because of the "ng-include='src'", where "src" is not replaced by the path... How to fix it ?

EDIT:

I tried to put this template :

template: '<div class="include-container" ng-include="{{ src }}"></div>'

but I get this error:

Error: [$parse:syntax] http://errors.angularjs.org/1.5.0/$parse/syntax?p0=%7B&p1=invalid%20key&p2=2&p3=%7B%7Brc%20%7D%7D&p4=%7B%src%20%7D%7D

EDIT 2: When I put this as a template :

template: '<div class="include-container" ng-include="tmpSrc"></div>'

and replace scope.src by scope.tmpSrc, the ng-include value is good now, BUT the replaced template in my html view is commented out... Why ?

EDIT 3:

Using your snippet, here is an idea of what I need to do:

  angular
    .module('app', [])
    .directive("myInclude", function($compile) {
      return {
        restrict: 'CAE',
        replace: true,
        scope: {
          src: '@',
        },
        link: function(scope, iElement, iAttrs, controller) {
          scope.$on("$includeContentError", function() {
            scope.src = 'error-template.html';
          });
          scope.$on("$includeContentLoaded", function(event, args) {
            // No need to do anything - the content has loaded.
          });
        },
        template: '<div class="include-container" ng-include="src"></div>'
      };
    })

  .controller('mainController', ['$scope', '$http',
    function($scope, $http) {

      // Is the content loaded ?
      $scope.state = 'loading';
    }
  ]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>

<div ng-app="app">
  <div>
    This will show the correct template:
    <my-include src="path/to/template.html"></my-include>
  </div>

  <div>
    This will show an error template:
    <div ng-controller="mainController as main">
      <my-include src="something/that/does/not/exist.html"></my-include>
    </div>
  </div>

  <script type="text/ng-template" id="path/to/template.html">

    <h1>I want to display state : {{ state }}</h1>
  </script>
  <script type="text/ng-template" id="error-template.html">
    <h1>Hello from "error-template.html"</h1>
  </script>
</div>
carndacier
  • 960
  • 15
  • 38

1 Answers1

2

Basically what you want is to "enhance" ngInclude in order to support and error fallback and not to create a new scope (since this may cause certain "bugs" with the scope's prototypical inheritance like this one, or this one, etc).

I've created a directive which does this. It loads a custom template specified via the src attribute and supports a fallback template option which is specified via the error-src attribute.

I'm not really a fan of this approach especially if you're adding logic in the template which depends on its parent. You should be delegating the template logic to focused and reusable directives. This will aid the testing process and hide the implementation details.

    angular
      .module('app', [])
      .controller('MainController', ['$scope',
        function($scope) {
          // Is the content loaded ?
          $scope.state = 'loading';
        }
      ])
      .directive('staticNgInclude', ['$compile', '$http', '$templateCache',
        function($compile, $http, $templateCache) {
          return {
            link: function(scope, iElement, iAttrs) {
              if (angular.isUndefined(iAttrs.src)) {
                throw 'staticNgInclude requires the src attribute.'
              }

              $http
                .get(iAttrs.src, {
                  cache: $templateCache
                }).then(function(response) {
                  // Hooray, the template was found!
                  $compile(iElement.html(response.data).contents())(scope);
                }, function() {
                  // Fetch the error template!
                  $http
                    .get(iAttrs.errorSrc || 'error-template.html', {
                      cache: $templateCache
                    }).then(function(response) {
                      $compile(iElement.html(response.data).contents())(scope);
                    });
                });
            },
            replace: false,
            restrict: 'E'
          };
        }
      ]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>

<div ng-app="app">
  <div ng-controller="MainController">
    This will show the correct template:
    <static-ng-include src="path/to/template.html"></static-ng-include>
  </div>
  <div>
    This will show an error template:
    <static-ng-include src="something/that/does/not/exist.html"></static-ng-include>
  </div>
  <script type="text/ng-template" id="path/to/template.html">
    <h1>I want to display state : {{ state }}</h1>
  </script>
  <script type="text/ng-template" id="error-template.html">
    <h1>Hello from "error-template.html"</h1>
  </script>
</div>
Community
  • 1
  • 1
Cosmin Ababei
  • 7,003
  • 2
  • 20
  • 34
  • I tried it already, but then I get an angular error, and my template is still not shown. I'll update my question with the error I get – carndacier Mar 07 '16 at 12:05
  • I've added an example of what I guess you're trying to achieve. – Cosmin Ababei Mar 07 '16 at 12:33
  • Basically, what I am doing is allow developers to use custom template, to override the default one - to allow that, they have to put their custom template under the folder "custom". If the include doesn't find the "custom" template, with the same view name, it loads the default one - that's the point of my .replace('/custom', '') Now my template is loaded... but the variable in its controller don't seem to be working (i have a "state" variable, that display the loader or the content. I've just a blank. and in the debugger, all the code is commented out... Thanks for your help mate ! – carndacier Mar 07 '16 at 12:43
  • Notice that your directive has an isolated scope. The 'isolated' scope differs from normal scope in that it does not prototypically inherit from its parent scope. If you could include a code snippet which we could run that would be great. – Cosmin Ababei Mar 07 '16 at 13:00
  • I've put a code snippet in my question. Hope you've got enough information ! – carndacier Mar 07 '16 at 13:27
  • That's perfect mate ! it works like a charm ;) Another question for you : is it possible to dynamically change the src ? I would like to change the included template by clicking a link / button – carndacier Mar 07 '16 at 15:14
  • Yes. You could [watch the **src** attribute for changes](http://stackoverflow.com/questions/15911300/is-it-possible-to-watch-attributes-changes) and whenever there's one then simply re-run the *http template fetching logic*. – Cosmin Ababei Mar 07 '16 at 15:21