Escaping & > characters in ng-bind in AngularJs

DarkKnight picture DarkKnight · Oct 9, 2013 · Viewed 27.8k times · Source

I have a use case, where we can have '&' and '>' characters in a string. eg. Johnson & Johnson, value > 3. So while the response from server is encoded, hence the value becomes 'value &ampgt 3'.

ng-bind doesn't support the following:

value > 3 will be rendered for ngBind, whereas the browser renders the same content as value > 3.

http://jsfiddle.net/HKahG/2/

Ng:bind <div ng-bind="model"></div> 
Ng:bind-html <div ng-bind-html="model"></div>
<div> From Div: value &gt; </div>

Why is this default browser behavior not present in ng-bind?. I don't want to use ng-bind-html (has issues with value < and it is not a html) or ng-bind-unsafe-html.

My application has dynamic key-value fields which will be displayed in different parts of the application. So it will require additional overhead to use a separate directive or decorator to display all string fields than to use ngBind.

Questions:

1) Is there any other way of doing the same without using an additional directive, or is this the right way of handling encoded data?

2) Can I override the behavior of ng-bind or decorate it by default?

Answer

J. Bruni picture J. Bruni · Oct 12, 2013

EDIT: please, go straight to the bottom of the answer to get the best version; the answer is at chronological order; I got the optimal code after a few iterations, at the end. Thank you.

  • Can I override the behaviour of ng-bind or decorate it by default ?

Yes. I've done a very simple implementation which makes ng-bind to behave as you want. Well... I'm not sure if this is exactly what you want, but at least it does what I've understood you want.

Working fiddle: http://jsfiddle.net/93QQM/

And here is the code:

module.directive('ngBind', function() {
    return {
        compile: function(tElement, tAttrs) {
            tAttrs.ngBind = 'myBind(' + tAttrs.ngBind + ')';
            return { 
                pre: function(scope) {
                    scope.myBind = function(text) {
                        return angular.element('<div>' + text + '</div>').text();
                    }
                }
            };
        }
    }
});

This is not exactly an "additional directive" - this is the way to "override the behaviour of ng-bind". It does not add a new directive, it just extends behaviour of existent ngBind directive.

At the compile function, we modify the value of the ng-bind attribute, wrapping it into a function call. With this, we have access to the original model value, and the opportunity to return it modified.

We make the function available through the scope in the pre-linking phase, because if we do this in the post-linking phase, the function will be available only after the original ngBind directive has retrieved the value from the attribute (which will be an empty string, because the function wil not be found).

The myBind function is simple and smart: it creates an element, and the text is used - unchanged - as the element body, only to be immediately retrieved through the text function - which will return the contents just as "the browser renders" it.

This way, you can use ngBind as usual, like <div ng-bind="model.content" />, but have this modified behaviour.


Improved version

Instead of attaching the myBind function to every scope where ngBind is applied, at every pre-linking phase, we can attach it only once to the $rootScope, making it immediately available for all scopes.

New working fiddle: http://jsfiddle.net/EUqP9/

New code:

module.directive('ngBind', ['$rootScope', function($rootScope) {
    $rootScope.myBind = function(text) {
        return angular.element('<div>' + text + '</div>').text();
    };
    return {
        compile: function(tElement, tAttrs) {
            tAttrs.ngBind = 'myBind(' + tAttrs.ngBind + ')';
        }
    };
}]);

Much cleaner than the previous version! Of course, you can change myBind function name to any other name you want. The "cost" of the feature is this: have this simple function added to the root scope - it is up to you to decide if it worths the price.


Yet another version

Influenced by Chemiv's answer... why not remove the function from any scope and make it a filter instead? It also works.

Yet another new working fiddle: http://jsfiddle.net/hQJaZ/

And the new code:

module.filter('decode', function() {
    return function(text) {
        return angular.element('<div>' + text + '</div>').text();
    };
}).directive('ngBind', function() {
    return {
        compile: function(tElement, tAttrs) {
            tAttrs.ngBind += '|decode';
        }
    };
});

Now you have three options to choose from the menu.