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 > 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 > </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?
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.
This is HTML:
>
It may not have HTML tags, but it's still HTML. If you want to use ng-bind
, your server needs to return unencoded text. Ie, >
instead of >
.
Use ng-bind-html
or modify your server to return plain text without html encoding it first.
Edit: Quick demo that illustrates the use of >
and >
in JavaScript:
div1.innerHTML = ">"; // write HTML
div2.textContent = ">"; // write plain text
console.log(div1.innerHTML === div2.innerHTML);
console.log(div1.textContent === div2.textContent);
http://jsfiddle.net/XhEcV/
ng-bind uses .text() method to replace text and while your code contains >
which is HTML markup it is not correctly rendered by ng-bind. You should use ng-bind-html in this place as you are actually entering HTML content. Otherwise you can replace > by regex to '>'.
ex :- model = model.replace(/>/g, '>');
But in this case you have to replace all HTML markups which is not needed since ng-bind-html is already working fine in your case.
Yes , let's "decorate" it with a filter:
.filter("decode",function(){
return function(str){
var el = document.createElement("div");
el.innerHTML = str;
str = el.textContent || el.innerText;
return str;
}
});
And use it like: <div ng-bind="model|decode"></div>
Working example: http://jsfiddle.net/HKahG/5/
Inspired by this answer: https://stackoverflow.com/a/784698/1206613
I remember a directive named ngBindHtmlUnsafe available for such use cases.
http://code.angularjs.org/1.0.8/docs/api/ng.directive:ngBindHtmlUnsafe
Please refer to this. Not sure if this is available in later unstable releases.
This is link to the latest stable release available.
Why not just use $sce.trustAsHtml?