angularjsdynamicangularjs-directiveangularjs-ng-includeangularjs-templates

AngularJS dynamic template with ng-include and separate template directives


I want to create a div which loads different templates dynamically, based on a context parameter:

My "search-results-container" directive:

app.directive("searchResultsContainer", function() {
  restrict: "E",
  templateUrl: "search-results-container.html",
  controller: function($scope) {
    $scope.templates = [
      { viewmode: "list", url: "search-results-list-view.html" },
      { viewmode: "grid", url: "search-results-grid-view.html" },
      { viewmode: "table", url: "search-results-table-view.html" }
    ]
  },
  link: function(scope) {
    scope.toggleView = function() {
      scope.templates.push(scope.templates.shift());
    }
  }
}

My "search-results-container.html" file:

<div ng-include="templates[0].url"></div>

My "search-results-table-view.html" file:

<table>
  <tr ng-repeat="item in ctrl.items">
    <td>{{ item.title }}</td>
  <tr>
</table>

Loading the templates dynamically works without a problem. However, I want these templates to run some "callback" function, once they are done loading.

I know there is the "onload" attribute, which I could add to the like so:

<div ng-include="templates[0].url" onload="onLoadFunction()"></div>

This means that I need to populate my original "search-results-container" directive with an "onLoadFunction" in the scope, which would have to use a switch (or similar technique) to distinguish between the template files and run a specific function depending on the currently active one - I want to prevent that because it is not clean.

I want to have separate directives for each template, like:

app.directive("searchResultsTableView", function() {
    return {
        restrict: "E",
        templateUrl: "search-results-table-view.html",
        controller: function($scope) {
            ...
        },
        link: function(scope) {
          scope.someOnLoadFunction = function() {
            /* Stuff to execute when template has loaded */
          }
        }
    }
});

If I do that, and I change my "search-results-table-view.html" accordingly to this:

<search-results-table-view>
  <table>
    <tr ng-repeat="item in ctrl.items">
      <td>{{ item.title }}</td>
    <tr>
  </table>
</search-results-table-view>

...I run into some sort of infinite loop or something and Angular / the browser crashes (becomes unresponsive). Is there a way to achieve what I plan, without filling up the container's directive with a ton of "onLoad" functions, one for each nested template?


Solution

  • What we ended up doing is this:

    we have one directive:

    app.directive("searchResultsContainer", function() {
      restrict: "E",
      templateUrl: "search-results-container.html",
      controller: function($scope) {
        $scope.templates = [
          { viewmode: "list", url: "search-results-list-view.html" },
          { viewmode: "grid", url: "search-results-grid-view.html" },
          { viewmode: "table", url: "search-results-table-view.html" }
        ]
      },
      link: function(scope) {
        scope.toggleView = function() {
          scope.templates.push(scope.templates.shift());
        }
      }
    }
    

    This directive is instantiated by using an ng-include directive:

    <search-results-container ng-include="views/result-list.html"></search-results-container>
    

    The used result-list.html file has this passage:

    <div ng-switch on="templates[0].viewmode">
        <search-results-list-view ng-switch-default></gs-search-results-list-view>
        <search-results-grid-view ng-switch-when="grid"></gs-search-results-grid-view>
        <search-results-table-view ng-switch-when="table"></gs-search-results-table-view>
    </div>
    

    And it has a button to toggle between views, using a simple ng-click directive:

    <button ng-click="toggleViewMode()">Toggle View</button>
    

    This button triggers the searchResultsContainer's toggleView method, described in the directive above, moving the elements of the templates array around:

    scope.toggleView = function() {
        scope.templates.push(scope.templates.shift());
    }
    

    The ng-switch directive listens for changes in the "viewmode" property of the first element of the templates array:

    ng-switch on="templates[0].viewmode" 
    

    Such a change happens when clicking the button, thus changing the first element in the array entirely, which naturally also results in a change of the "viewmode" property, monitored by ng-switch.

    The nested ng-switch-default and ng-switch-when directives react to the change and display the according element, which has the appropriate value set in "ng-switch-when".

    The three children of the ng-switch each use a separate directive, here is one of them:

    app.directive("searchResultsListView", function() {
        return {
            restrict: "E",
            require: "^searchResultsContainer",
            template: "<div ng-include=\"'views/list-view.html'\"></div>",
            controller: function($scope) {
                /* $scope stuff goes here */
            },
            link: function(scope, element, attrs, searchResultsCtrl) {
                /* link stuff goes here */
            }
        }
    });
    

    Note the very important escaped quotation marks, followed by high-commas in the template's ng-include directive, as required by the ng-include directive (more info on ng-include).