85

Kinda new to angular. Is it possible to replace the ng-include node with the contents of the included template? For example, with:

<div ng-app>
    <script type="text/ng-template" id="test.html">
        <p>Test</p>
    </script>
    <div ng-include src="'test.html'"></div>
</div>

The generated html is:

<div ng-app>
    <script type="text/ng-template" id="test.html">
        <p>Test</p>
    </script>
    <div ng-include src="'test.html'">
        <span class="ng-scope"> </span>
        <p>Test</p>
        <span class="ng-scope"> </span>
    </div>
</div>

But what I want is:

<div ng-app>
    <script type="text/ng-template" id="test.html">
        <p>Test</p>
    </script>
    <p>Test</p>
</div>
SunnySydeUp
  • 6,680
  • 4
  • 28
  • 32
  • 1
    remove single quotes from `'test.html'` to use template rather than url – charlietfl May 11 '13 at 18:58
  • 1
    reading the comments of the doc for ng-include, it seems it's surround the string with single quotes for template and without for url. Also, related [stackoverflow question](http://stackoverflow.com/questions/13943471/angularjs-ng-include) – SunnySydeUp May 11 '13 at 22:22
  • you are reading docs and post you linked to backwards – charlietfl May 12 '13 at 05:06

7 Answers7

134

I had this same issue and still wanted the features of ng-include to include a dynamic template. I was building a dynamic Bootstrap toolbar and I needed the cleaner markup for the CSS styles to be applied properly.

Here is the solution that I came up with for those who are interested:

HTML:

<div ng-include src="dynamicTemplatePath" include-replace></div>

Custom Directive:

app.directive('includeReplace', function () {
    return {
        require: 'ngInclude',
        restrict: 'A', /* optional */
        link: function (scope, el, attrs) {
            el.replaceWith(el.children());
        }
    };
});

If this solution were used in the example above, setting scope.dynamicTemplatePath to 'test.html' would result in the desired markup.

colllin
  • 9,442
  • 9
  • 49
  • 65
Brady Isom
  • 1,635
  • 2
  • 13
  • 11
  • Requires angular v1.2+ – colllin Jan 20 '14 at 20:52
  • I've referenced this answer in a similar question: [angularjs - Avoid using extra DOM nodes when using nginclude - Stack Overflow](http://stackoverflow.com/questions/17589358/avoid-using-extra-dom-nodes-when-using-nginclude/24921498#24921498) – Peter V. Mørch Aug 18 '14 at 17:47
  • This works for angular 1.2.5+. For 1.2.4, if you have one ng-include that ng-includes another, it fails. I'm guessing because of [#5247](https://github.com/angular/angular.js/issues/5247), but I'm not sure. See [Changelog](https://github.com/angular/angular.js/blob/master/CHANGELOG.md#125-singularity-expansion-2013-12-13) for yourself. Here is a [Plunkr](http://plnkr.co/edit/vAPJLYc4sW2dTOekBFwM?p=preview) demonstrating this problem with 1.2.4 (change to angular 1.2.5 and see it works! :-) – Peter V. Mørch Aug 18 '14 at 18:18
  • I had similar solution but this is even simpler, GREAT! – Luckylooke Oct 14 '14 at 19:49
  • Great, waiting for make further analysis, but for the moment, using it too ! Thanks ! – avcajaraville Apr 27 '15 at 13:08
  • 9
    Take a note that such DOM manipulation pretty hacky.There is problem if root element of included template uses something like ng-repeat. It will not able to insert results in DOM. – Guria Jul 07 '15 at 13:10
  • I would add `priority: Number.MIN_SAFE_INTEGER` to the `includeReplace` directive to make sure it's executed last or at least `terminal: true` to make sure no other directives are executed on the removed element. – Masadow Jul 29 '15 at 09:36
  • Is there a way to change template while using this solution? I tried this but previous template remains on page. http://jsfiddle.net/U3pVM/19880/ I removed `require: 'ngInclude'` in jsfiddle because of error. – Anton Nov 02 '15 at 13:28
  • 1
    Please see my answer to this, this will fail because the prelink function will already have run in child elements. – Sai Dubbaka Nov 03 '15 at 20:02
  • @SaiDubbaka you da real MVP – Saad Benbouzid Jun 21 '16 at 14:04
  • I would stay away from this. I had been using this in my code but using include-replace when including a template will bork any ng-repeats in that template. Not a fun time figuring that out months later. – honkskillet Nov 21 '16 at 02:56
28

So thanks to @user1737909, I've realized that ng-include is not the way to go. Directives are the better approach and more explicit.

var App = angular.module('app', []);

App.directive('blah', function() {
    return {
        replace: true,
        restrict: 'E',  
        templateUrl: "test.html"
    };
});

In html:

<blah></blah>
SunnySydeUp
  • 6,680
  • 4
  • 28
  • 32
  • 2
    thanks! was looking for an ng-include solution but this helped me realize directives are better – Matt Kim Feb 23 '14 at 21:25
  • Keep in mind that `replace:true` in templates is [marked for deprecation](https://docs.angularjs.org/api/ng/service/$compile). I'd avoid using this solution due to the deprecation status. – Peter V. Mørch Aug 18 '14 at 17:48
  • @PeterV.Mørch Thanks. For those interested, this is the commit: https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb. It seems like it was deprecated because of it's complexity (and perhaps other reasons). – SunnySydeUp Aug 18 '14 at 22:08
15

I had the same problem, my 3rd party css stylesheet didn't like the extra DOM-element.

My solution was super-simple. Just move the ng-include 1 up. So instead of

<md-sidenav flex class="md-whiteframe-z3" md-component-id="left" md-is-locked-open="$media('gt-md')">
  <div ng-include="myService.template"></span>
</md-sidenav>

I simply did:

<md-sidenav flex class="md-whiteframe-z3" md-component-id="left" md-is-locked-open="$media('gt-md')" ng-include="myService.template">
</md-sidenav>

I bet this will work in most situations, even tho it technically isn't what the question is asking.

xeor
  • 5,301
  • 5
  • 36
  • 59
10

Another alternative is to write your own simple replace/include directive e.g.

    .directive('myReplace', function () {
               return {
                   replace: true,
                   restrict: 'A',
                   templateUrl: function (iElement, iAttrs) {
                       if (!iAttrs.myReplace) throw new Error("my-replace: template url must be provided");
                       return iAttrs.myReplace;
                   }
               };
           });

This would then be used as follows:

<div my-replace="test.html"></div>
Daniel Egan
  • 456
  • 5
  • 4
9

This is the correct way of replacing the children

angular.module('common').directive('includeReplace', function () {
    return {
        require: 'ngInclude',
        restrict: 'A',
        compile: function (tElement, tAttrs) {
            tElement.replaceWith(tElement.children());
            return {
                post : angular.noop
            };
        }
    };
});
Sai Dubbaka
  • 621
  • 7
  • 11
  • 4
    My included partial html got some ng-repeat, and this is the only answer resolving them ! Thanks a lot. – Saad Benbouzid Jun 21 '16 at 14:05
  • I had to move the function content from `compile` to `link`, because my element was empty during the compile stage. – itachi Sep 12 '17 at 20:44
3

Following directive extends ng-include native directive functionality.

It adds an event listener to replace the original element when content is ready and loaded.

Use it in the original way, just add "replace" attribute:

<ng-include src="'src.html'" replace></ng-include>

or with attribute notation:

<div ng-include="'src.html'" replace></div>

Here is the directive (remember to include 'include-replace' module as dependency):

angular.module('include-replace', []).directive('ngInclude', function () {
    return {
        priority: 1000,
        link: function($scope, $element, $attrs){

            if($attrs.replace !== undefined){
                var src = $scope.$eval($attrs.ngInclude || $attrs.src);

                var unbind = $scope.$on('$includeContentLoaded', function($event, loaded_src){
                    if(src === loaded_src){
                        $element.next().replaceWith($element.next().children());
                        unbind();
                    };
                });
            }
        }
    };
});
edrian
  • 4,501
  • 5
  • 31
  • 35
2

I would go with a safer solution than the one provided by @Brady Isom.

I prefer to rely on the onload option given by ng-include to make sure the template is loaded before trying to remove it.

.directive('foo', [function () {
    return {
        restrict: 'E', //Or whatever you need
        scope: true,
        template: '<ng-include src="someTemplate.html" onload="replace()"></ng-include>',
        link: function (scope, elem) {
            scope.replace = function () {
                elem.replaceWith(elem.children());
            };
        }
    };
}])

No need for a second directive since everything is handled within the first one.

Masadow
  • 762
  • 5
  • 15
  • beware that with angular 1.5 the first child in the directive element is a comment. So I made sure I get the ng-include element and then replace it with its children: `let ngInclude = angular.element( element[ 0 ].querySelector( 'ng-include' ) ); ngInclude.replaceWith( ngInclude.children() ); ` – Mattijs Jul 15 '16 at 05:36