44

I would like to use the same HTML template in 3 places, just each time with a different model. I know I can access the variables from the template, but there names will be different.

Is there a way to pass a model to the ngInclude?

This is what I would like to achieve, of course the attribute add-variable does not work now. Then in my included template, I would acces the detailsObject and its properties.

<pane title="{{projectSummary.ProjectResults.DisplayName}}">
    <h2>{{projectSummary.ProjectResults.DisplayName}}</h2>
    <ng-include src="'Partials/SummaryDetails.html'" init-variable="{'detailsObject': projectSummary.ProjectResults}"></ng-include>
</pane>

<pane  title="Documents" header="true"></pane>

<pane ng-repeat="document in projectSummary.DocumentResults" title="{{document.DisplayName}}">
    <h2>{{document.DisplayName}}</h2>
    <ng-include src="'Partials/SummaryDetails.html'" add-variable="{'detailsObject': document}"></ng-include>
</pane>

<pane ng-repeat="header in [1]" title="Languages" header="true"></pane>

<pane ng-repeat="language in projectSummary.ResultsByLanguagePairs" title="{{language.DisplayName}}">
    <h2>{{document.DisplayName}}</h2>
    <ng-include src="'Partials/SummaryDetails.html'" add-variable="{'detailsObject': language}"></ng-include>
</pane>

If I took a bad approach with using ng-include, is there something else I should try?

Tomas Grosup
  • 6,396
  • 3
  • 30
  • 44

6 Answers6

61

There is a rather simple solution, although I must admit, it's not what Misko would recommend. But if creating a directive is an overkill for you and getting Brice's patch is not feasible then the following will help you.

<div ng-repeat="name in ['A']" ng-include="'partial.html'"></div>
<div ng-repeat="name in ['B']" ng-include="'partial.html'"></div>

<script type="text/ng-template" id="partial.html">
   <div>{{ name }}</div>
</script>

It's quite evident why it works. See an example here: http://jsfiddle.net/Cndc6/4/

Den
  • 1,827
  • 3
  • 25
  • 46
Denis Pshenov
  • 11,157
  • 6
  • 42
  • 42
21

NOTE: this is not my original answer but this is how I'd do this after using angular for a bit.

I would create a directive with the html template as the markup passing in the dynamic data to the directive as seen in this fiddle.

Steps/notes for this example:

  1. Define a directive with markup in the templateUrl and attribute(s) used to pass data into the directive (named type in this example).
  2. Use the directive data in the template (named type in this example).
  3. When using the directive in the markup make sure you pass in the data from the controller scope to the directive (<address-form type="billing"></address-form> (where billing is accessing an object on the controller scope).
  4. Note that when defining a directive the name is camel cased but when used in the markup it is lower case dash delimited (ie it's named addressForm in the js but address-form in the html). More info on this can be found in the angular docs here.

Here is the js:

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

angular.module('myApp').directive('addressForm', function() {
    return {
        restrict: 'E',
        templateUrl: 'partials/addressform.html', // markup for template
        scope: {
            type: '=' // allows data to be passed into directive from controller scope
        }
    };
});

angular.module('myApp').controller('MyCtrl', function($scope) {
    // sample objects in the controller scope that gets passed to the directive
    $scope.billing = { type: 'billing type', value: 'abc' };
    $scope.delivery = { type: 'delivery type', value: 'def' };
});

With markup:

<div ng-controller="MyCtrl">
    <address-form type="billing"></address-form>
    <address-form type="delivery"></address-form>
</div>

ORIGINAL ANSWER (which is completely different than using a directive BTW).

Note: The fiddle from my original answer below doesn't appear to work anymore due to an error (but keeping it here in case it is still useful)

There was a discussion about this on the Google Group you can see it here.

It looks like this functionality is not supported out of the box but you can use Brice's patch as described in this post.

Here is the sample code from his jsfiddle:

<script id="partials/addressform.html" type="text/ng-template">
    partial of type {{type}}<br>
</script>

<div ng-controller="MyCtrl">
  <ng-include src="'partials/addressform.html'" onInclude="type='billing'"></ng-include>
  <ng-include src="'partials/addressform.html'" onLoad="type='delivery'"></ng-include>
</div>
Gloopy
  • 37,767
  • 15
  • 103
  • 71
  • 1
    Thank you. I managed to achieve it without a new directive, with a ng-init. In this ng-init, I create the objectDetails variable and init it to what I want. – Tomas Grosup Nov 16 '12 at 19:35
  • Nowadays you can use your own _directive_ and in it use the _templateUrl_ property. – Benny Bottema Jan 12 '13 at 23:15
  • using `type='delivery'` just gives me the word `delivery`, not the `delivery` value from the controller. -1 – CodyBugstein Nov 27 '14 at 08:08
  • 1
    @Imray I've changed the answer to use a directive instead which should fix the issue you were having with just passing a word instead of the value from the controller. Thanks for letting me know the existing answer was not quite working anymore. – Gloopy Dec 02 '14 at 17:14
  • @Gloopy I need to add some other data along with billing also. Let say some $scope.name="Ankur" and I cannot afford to do type="name" as it will generate the view again. What to do in this regard? – Ankur Aggarwal May 29 '15 at 11:50
  • @AnkurAggarwal does [this fiddle](http://jsfiddle.net/NMmyY/164/) help at all? you can add as many properties to the object being bound. – Gloopy May 29 '15 at 16:05
  • @Gloopy I don't want it in the same object due to some restrictions. But i want to use certain scope property inside the view. – Ankur Aggarwal May 29 '15 at 17:00
  • @AnkurAggarwal you can pass in more data via a different object to the directive [like this](http://jsfiddle.net/NMmyY/165/) – Gloopy May 29 '15 at 17:59
  • I've been putting off learning directives for some time. This was a great introduction into it. I'll be using directives liberally in the future. I'd recommend this approach even if you haven't touched directives before. – V Maharajh Sep 29 '15 at 17:01
14

There is a pull to fix this but it looks like it's dead: https://github.com/angular/angular.js/pull/1227

Without modifying the Angular source code this will solve the problem in a reusable not-too-hacky-feeling way:

directive('newScope', function() {
    return {
        scope: true,
        priority: 450,
    };
});

And an example:

<div new-scope ng-init="myVar = 'one instance'" ng-include="'template.html'"></div>
<div new-scope ng-init="myVar = 'another instance'" ng-include="'template.html'"></div>

Here is a Plunker of it in action: http://plnkr.co/edit/El8bIm8ta97MNRglfl3n

John Culviner
  • 22,235
  • 6
  • 55
  • 51
  • 1
    Works great, thank you. For me it looks nicer if I get rid of _ng-init_ and just have **
    **. (I just copied two lines of code from _ng-init_ to your directive to get this to work)
    – Ande Aug 13 '14 at 17:06
6
<div new-scope="myVar = 'one instance'" ng-include="'template.html'"></div>

directive('newScope', function () {
    return {
        scope: true,
        priority: 450,
        compile: function () {
            return {
                pre: function (scope, element, attrs) {
                    scope.$eval(attrs.newScope);
                }
            };
        }
    };
});

This is a directive that combines new-scope from John Culviner's answer with code from Angular's ng-init.

For completeness, this is the Angular 1.2 26 ng-init source, you can see the only change in the new-scope directive is the addition of scope: true

{
  priority: 450,
  compile: function() {
    return {
      pre: function(scope, element, attrs) {
        scope.$eval(attrs.ngInit);
      }
    };
  }
}
Community
  • 1
  • 1
Ande
  • 491
  • 1
  • 7
  • 22
5

Quick'n'dirty solution:

<div ng-init="details=document||language||projectSummary.ProjectResults">
Tomas Grosup
  • 6,396
  • 3
  • 30
  • 44
  • Not a bad workaround so your div is outside each ng-include and you are setting each one explicitly to either documents, language, or projectSummary.ProjectResults? – Gloopy Nov 16 '12 at 21:16
  • Actually i put this inside the html template, because I am lazy. But writing it before each ng-Include div would be cleaner. – Tomas Grosup Nov 17 '12 at 09:35
  • Would this init the value in correct scope? I.e. does it set the value in the scope of the include? – uriDium Sep 17 '13 at 07:57
  • This line is actually inside the inner html template, so yes, the scope is correct. – Tomas Grosup Sep 17 '13 at 13:06
  • This is what i was literally looking for i have no idea why i didn't know this already lol. Though this is the best answer on this question so far. – Playdome.io Apr 27 '17 at 00:09
1

I hear you! ng-include is not that reusable because it has access to the global scope. It's a little weird.

There should be a way to set local variables. Using a new directive instead of ng-include is a cleaner solution.

The ideal usage looks like:

<div ng-include-template="'Partials/SummaryDetails.html'" ng-include-variables="{ 'detailsObject': language }"></div>

The directive is:

.directive(
  'ngIncludeTemplate'
  () ->
    {
      templateUrl: (elem, attrs) -> attrs.ngIncludeTemplate
      restrict: 'A'
      scope: {
        'ngIncludeVariables': '&'
      }
      link: (scope, elem, attrs) ->
        vars = scope.ngIncludeVariables()
        for key, value of vars
          scope[key] = value
    }
)

You can see that the directive doesn't use the global scope. Instead, it reads the object from ng-include-variables and add those members to its own local scope.

It's clean and generic.

Tanin
  • 1,853
  • 1
  • 15
  • 20