Raymond Holguin Raymond Holguin - 6 months ago 30
AngularJS Question

AngularJS: Compile angular code in strings without scope

I have a angular service that manages all my UI tabs. It contains an object that has all the information I need about a tab such as icons, labels, URL, etc. I have a use case for one my tabs to have a dynamic counter on it but I can't figure out how to get it working.

Service snippet:

angular.module('app.services').service('tabService', function($rootScope, $route, $location) {

var tabs = {
"/front": {
"search": {
label: "Search",
url: "/search",
classIcon: "fa fa-search",
urlMatchAddt: ['/searchResults','/productDetails']
},
"order": {
label: "Order",
url: "/cart",
classIcon: "fa fa-shopping-bag"
} ....


HTML Code: index.html (tabService is injected into a my BaseCtrl controller)

<body ng-app="app" ng-controller="BaseCtrl">
...
<li ng-repeat="t in tabService.getTabs()" ng-class="{active: tabService.isActiveTab(t.url)}" ng-cloak>
<a href="#{{t.url}}"><i class="{{t.classIcon}}" aria-hidden="true" ng-bind-html=""></i>&nbsp;<span ng-bind-html="t.label | trustHTML"></span></a>
</li>
....
</body>


So what I am trying to do for example is in the
label
field of one of my tabs I want to put something like

label: "Order - {{counter}}"


So every time I update that
{{counter}}
variable then my label will also refresh. The
label
can also contain HTML code which is why I am using
ng-bind-html
directive.

Currently I am doing an ugly
$watch
on the variable and when it changes then I am manually just completely overwriting the label value with a new string that includes the updated value.

I have tried using
$compile
but I cannot use it with
$rootScope
and I cannot pass
$scope
into my service. I am unsure what the best solution is.

Any ideas?

AngularJS: 1.6

Answer Source

Basically there's a need for a directive/component instead of <a ...>...</a>.

There's nothing ugly in $watch itself but doing this in one single controller would be certainly ugly.

To calculate strings like Order - {{counter}}, $compile is total overkill but $interpolate service comes to rescue. Its sole purpose is to to what's required, interpolate Angular expressions to strings.

It likely should be something like

app.directive('tab', function () {
  return {
    scope: {
      tabData: '<',
      counter: '<'
    },
    template: '<a ...>...<span ng-bind-html="interpolatedLabel | trustHTML"></span></a>',
    controller: function ($interpolate, $scope) {
      $scope.$watchGroup(['tabData.label', 'counter'], function () {
        $scope.interpolatedLabel = $interpolate($scope.tabData.label)({
          counter: $scope.counter
        });
      }
    }
  }
});

and

<li ng-repeat="t in tabService.getTabs()"...>
  <tab tab-data="t" counter="$index"></tab>
</li>