Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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>
like image 700
Amicable Avatar asked Jul 27 '15 14:07

Amicable


People also ask

What is the difference between Ng-bind and expression?

firstName expression in the application's scope are also reflected instantly in the DOM by Angular. ng-bind is about twice as fast as {{}} expression bind. ng-bind places a watcher on the passed expression and therefore the ng-bind only applies, when the passed value actually changes.

How does ng-bind HTML work?

The AngularJS ng-bind-html directive is used to bind content to an HTML element securely. It evaluates the expressions and inserts the resulting HTML into the element in a secure way. By default, the resulting HTML content will be sanitized using the $sanitize service.

What are the differences between Ng-bind and in AngularJS?

ng-bind is also used for data binding but unlike ng-bind it supports only one way data binding.It is used for displaying model properties as innerHTML of html elements such as div and span. This is unlike ng-model which is used for binding input fields and supports two way data binding.

Does ng-bind bind the application data to HTML tags in AngularJS?

ng-bind directive binds the AngularJS Application data to HTML tags. ng-bind updates the model created by ng-model directive to be displayed in the html tag whenever user input something in the control or updates the html control's data when model data is updated by controller.


1 Answers

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);
};
like image 83
Jonathan Gawrych Avatar answered Sep 27 '22 20:09

Jonathan Gawrych