Search code examples
angularjsangularjs-directiveangularjs-filterangularjs-compile

AngularJS string replacement and $compile


New to AngularJS - I am trying to create a function that finds "tokens" of specific text, replaces them with an ng-click directive bound to a function on the controller. For example, from my database I have lots of text-fields with something like this:

<p>Your [labor] cost is included in price</p>

Which I would like to end up like this:

<p>Your <a href="#" ng-click="showModal('labor')">labor</a> cost is included in price.</p>

I have no problem replacing the [labor] token with a filter, but I am a bit lost on how to incorporate $compile to bind the ng-click to $scope.showModal() on my controller.

Any help would be appreciated. Thank you.

My existing filter:

myApp.filter('parseHtml', function ($sce) {

    return function (text) {

        var link = '<a href="" ng-click="getModal(\'labor\')">Labor</a>';
        var replaced = text.replace(/\[labor\]/g, link);

        return $sce.trustAsHtml(replaced);
    };
});

In the html

<span ng-bind-html="package.price | parseHtml"></span>

The controller

myApp.controller('MainController',
    function MainController($scope, $http) {

        $scope.getpackage = function (slug) {

            var onpackageComplete = function (response) {
                $scope.package = response.data;
            };
            $http.get('/packages/api/' + slug + '/detail')
                .then(onpackageComplete);
        };

        $scope.getModal = function (name) {

            $scope.modalVisible = true;
            if (name === 'labor') {
                $scope.modalBody = '/path/to/html/snippet/ModalLabor.html';
            } else if (name === '') {
                $scope.modalBody = '';
            }

        };
    }
);

Solution

  • Taking as reference the accepted answer provided in Compiling dynamic HTML strings from database, this should do the trick

    Change your filter for a directive instead, which takes the content with the token, replace it with the click function and compiles the content, putting in the DOM again. See below demo carefully in order to see how to do it.

    angular
      .module('app', [])
    
      //The new directive! (which replaced the old filter)
      .directive('parseHtml', function($compile) {
        return {
          restrict: 'A',
          replace: true,
          link: function(scope, iElem, attrs) {
            var link = '<a href="" ng-click="getModal(\'labor\')">Labor</a>';
    
            scope.$watch(attrs.parseHtml, function(text) {
              if (text) {
                var replaced = text.toString().replace(/\[labor\]/g, link);
                iElem.html(replaced);
                $compile(iElem.contents())(scope);
              }
            })
          }
        }
      })
    
      //sample controller
      .controller('MainController',
        function MainController($scope, $http) {
          var p = 1;
          
          $scope.getpackage = function() {debugger;
            $scope.package = {
              price: "Value is " + p + " hundred - [labor]"
            };
            p = p + 1;
          };
    
          $scope.getModal = function(name) {
            console.log('getModal clicked!');
            alert('getModal clicked!');
          };
        }
      );
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    
    <div ng-app="app" ng-controller="MainController">
    
      <!--mock request-->
      <button ng-click="getpackage()"> Get Package</button> <br /> <br />
    
      <!-- how to use the directive -->
      <span parse-html="package.price"></span>
    
    </div>

    Important: The action in the link is getModal (fixed in the directive). If you need this do be added dynamically too, you need to pass that function as argument to the directive too. That new implementation would require some changes.