Why do ng-bind-html and $sanitize produce different results?
Asked Answered
F

1

8

I'm trying to sanitize the content of some text areas, I cannot use ng-bind-html because it breaks two way binding (ng-model does not work at the same time)

Strangely when I apply ng-bind-html to a model it produces a different result to when I use $sanitize or $sce inside of a directive.

Here's a sample I made up

http://plnkr.co/edit/iRvK4med8T9Xqs22BkOe?p=preview

First text area uses ng-bind-html, the second uses $sanitize and the third should be the code for the ng-bind-html directive as I ripped out of the AngularJS source code.

" is only corrected changed to " when using ng-bind-html, in the other two examples it changes to "

How can I replicate the results of ng-bind-html in my directive - while keeping the two way binding?

angular.module('sanitizeExample', ['ngSanitize'])
  .controller('ExampleController', ['$scope', '$sce',
    function($scope, $sce) {

      $scope.value = 'This in "quotes" for testing';
      $scope.model = 'This in "quotes" for testing';

    }
  ]).directive('sanitize', ['$sanitize', '$parse', '$sce',
    function($sanitize, $parse, $sce) {
      return {
        restrict: 'A',
        replace: true,
        scope: true,
        link: function(scope, element, attrs) {

          var process = function(input) {
            return $sanitize(input);
            //return $sce.getTrustedHtml(input);
          };

          var processed = process(scope.model);
          console.log(processed); // Output here = This in "quotes" for testing
          $parse(attrs.ngModel).assign(scope, processed);
          //element.html(processed);
        }
      };
    }
  ])
  .directive('sanitizeBindHtml', ['$parse', '$sce',
    function($parse, $sce) {
      return {
        restrict: 'A',
        replace: true,
        scope: true,
        link: function(scope, element, attrs) {

          var parsed = $parse(attrs.ngModel);

          function getStringValue() {
            var value = parsed(scope);
            getStringValue.$$unwatch = parsed.$$unwatch;
            return (value || '').toString();
          }

          scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) {
            var processed = $sce.getTrustedHtml(parsed(scope)) || '';

            $parse(attrs.ngModel).assign(scope, processed)
          });
        }
      };
    }
  ]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular-sanitize.js"></script>

<!doctype html>
<html lang="en">


<body ng-app="sanitizeExample">

  <div ng-controller="ExampleController">
    <textarea ng-bind-html="value"></textarea>
    <br/>{{value}}
    <br/>
    <br/>
    <textarea sanitize ng-model="model"></textarea>
    <br/>
    <br/>
    <textarea sanitize-bind-html ng-model="model"></textarea>

  </div>
</body>
Finch answered 27/7, 2015 at 14:44 Comment(3)
This is interesting. The ngModel directive seems to be causing issues here for you, and setting priority won't fix it. In your final example, copying ngBindHtml, if you replace ngModel with another name (e.g. bob, bob="model"), it will work. plnkr.co/edit/eVA9lvMmwOcWKL1B2fjM?p=preview . Similar for your second directive, except it requires a few other minor changes re: parsing.Mcardle
Curious to hear a full answer from somebody with more experience with, or the time to dig into ngModel directive. If that doesn't happen, I'll dig into that source when I have a bit more time :) . For the time being, as I mentioned, updating to another attribute works for your other directive, too plnkr.co/edit/lVH1IQAhMfAot116xfiM?p=previewMcardle
I will have to see what I can dig up on ngModel when I have a chance. It's a shame changing the attribute doesn't allow two way binding to function still!Finch
F
2

It turns out like we would expect, the sanitation service is returning the same result. Placing a breakpoint inside the ngBindHtmlDirective, We can step in and see what is happening. We dive in and examine the values inside the $SanitizeProvider. The value of buf that will be returned back to the ngBindHtmlDirective is:

This in &#34;quotes&#34; for testing

The exact same as we get for calling $sanitize, so what's the real difference? The real difference is between a textbox's innerHTML and value. View this example plunker. You can see the difference between calling the two different methods, with the different ways of escaping a double quote. I didn't go digging though the w3 spec or the browser code, but I assume the innerHTML assignment is doing additional work under the hood of creating a documentFragment, grabbing it's textContent, then assigning that to the textbox's value. Obviously value is just grabbing the string and inserting it as is.


So what's the problem with your directives? I see that element.html(processed) is in a comment, but uncommenting it doesn't have an affect. Well the truth is that it does work for a split second! Stepping though with the debugger, the value of the textbox is correctly set, but then a $digest cycle gets fired and immediate changes it! The truth is the ngModelDirective is getting in the way, specifically it's the $render function of the baseInputType. We can see in the code it is using the element.val method.

How can we fix this in the directives? Require the ngModelController and override its $render function to use element.html method instead (example plunker).

// Add require to get the controller
require: 'ngModel',

// Controller is passed in as the 4th argument
link: function(scope, element, attrs, ngModelCtrl) {

// modify the $render function to process it as HTML
ngModelCtrl.$render = function() {
    element.html(ngModelCtrl.$isEmpty(ngModelCtrl.$viewValue) ? '' : ngModelCtrl.$viewValue);
};
Foiled answered 13/2, 2016 at 6:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.