Search code examples
angularjsangular-ui-bootstrapangular-ui-bootstrap-tab

Dynamic Content in Dynamic Tab (Angular, UI Bootstrap)


I'd like to use ng-include in the content of a dynamically generated tab using AngularJs and UI Bootstrap.

I have a Plunker here: http://plnkr.co/edit/2mpbovsu2eDrUdu8t7SM?p=preview

<div id="mainCntr" style="padding: 20px;">
  <uib-tabset>
    <uib-tab ng-repeat="tab in tabs" active="tab.active" disable="tab.disabled">
      <uib-tab-heading>
        {{tab.title}} <i class="glyphicon glyphicon-remove-sign" ng-click="removeTab($index)"></i>
      </uib-tab-heading>
      {{tab.content}}
    </uib-tab>
  </uib-tabset>
</div>

JS Code:

$scope.addTab = function() {
    var len = $scope.tabs.length + 1;
    var numLbl = '' + ((len > 9) ? '' : '0') + String(len);

    var mrkUp = '<div>' +
        '<h1>New Tab ' + numLbl + ' {{foo}}</h1>' + 
        '<div ng-include="tab.tabUrl" class="ng-scope"></div>' +
        '</div>';

    $scope.tabs.push({title: 'Tab ' + numLbl, content: $compile(angular.element(mrkUp))($scope)});
}

In the Plunker, click the "Add Tab" button. It calls a function in $scope that pushes a new tab to the collection but passing in some dynamically generated content that includes a ng-include directive. The expected output is that the ng-include will be displayed inside of the tab content area.

Thanks


Solution

  • In your Plunker you are using ng-bind-html which doesn't compile the HTML for you. You can create a new directive that does that for you.

    Source code for ng-bind-html:

    var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
      return {
        restrict: 'A',
        compile: function ngBindHtmlCompile(tElement, tAttrs) {
          var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
          var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
            return (value || '').toString();
          });
          $compile.$$addBindingClass(tElement);
    
          return function ngBindHtmlLink(scope, element, attr) {
            $compile.$$addBindingInfo(element, attr.ngBindHtml);
    
            scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
              // we re-evaluate the expr because we want a TrustedValueHolderType
              // for $sce, not a string
              element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
            });
          };
        }
      };
    }];
    

    Pick a name for the new directive, for example compile-html.

    Replace tAttrs.ngBindHtml with tAttrs.compileHtml (or whatever name you picked).

    You need to replace $sce.getTrustedHtml with $sce.trustAsHtml, or you will get Error: [$sce:unsafe] Attempting to use an unsafe value in a safe context.

    Then you need to call $compile:

    $compile(element.contents())(scope);
    

    Full directive:

    app.directive('compileHtml', ['$sce', '$parse', '$compile',
      function($sce, $parse, $compile) {
        return {
          restrict: 'A',
          compile: function ngBindHtmlCompile(tElement, tAttrs) {
            var ngBindHtmlGetter = $parse(tAttrs.compileHtml);
            var ngBindHtmlWatch = $parse(tAttrs.compileHtml, function getStringValue(value) {
              return (value || '').toString();
            });
            $compile.$$addBindingClass(tElement);
    
            return function ngBindHtmlLink(scope, element, attr) {
              $compile.$$addBindingInfo(element, attr.compileHtml);
    
              scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
    
                element.html($sce.trustAsHtml(ngBindHtmlGetter(scope)) || '');
                $compile(element.contents())(scope);
              });
            };
          }
        };
      }
    ]);
    

    Usage:

    <div compile-html="tab.content"></div>
    

    Demo: http://plnkr.co/edit/TRYAaxeEPMTAay6rqEXp?p=preview