Is there an angular JS command that will do HTML escaping on text? I am processing a custom directive and have need to escape some of the output which it generates.
Internally the AngularJS sanitzer uses a encodeEntities function, but does not expose it. I know I could duplicate the function, but it seems like there should be a standard way of doing this.
Use-Case: I have a custom directive which does language localization. This directive uses a key lookup from a data file to find language text. In some cases this text is allowed to contain HTML, and/or the directive produces HTML to improve the resulting visual formatting. In addition this directive takes Angular expressions as parameters and uses them as replacements for tokens in the strings. I need to encode these parameters as they may not be HTML safe.
The directive is called as an attribute, for example i18n-html='welcome_text_html,1+1,user.name'. The directive then formats the string as described and uses element.html to update the DOM node.
This answer is about escaping, not sanitizing HTML. There are two approaches:
As mentioned by #maniekq, if you can work on the DOM, do:
element.text( scope.myValue );
From this answer, you can use this code from mustache.js and e.g. create an angular filter:
angular.module('myModule').filter('escapeHtml', function () {
var entityMap = {
"&": "&",
"<": "<",
">": ">",
'"': '"',
"'": ''',
"/": '/'
};
return function(str) {
return String(str).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
});
Sanitizing is one thing, but to display all characters and not "execute" HTML code I have used "text" function to set value.
In your directive, to set value, instead of writing:
element.html( scope.myValue );
write:
element.text( scope.myValue );
This answer is derived from #mb21's. The only thing that is changed is utilizing $sce. So you can use this filter in ng-bind-html, without triggering Error: $sce:unsafe.
angular
.module('yourModule', [
'ngSanitize'
])
.filter('escapeHtml', function ($sce) {
// Modified by Rockallite: Add $sce.trustAsHtml() to mute "Error: $sce:unsafe"
// http://stackoverflow.com/a/32835368/2293304
// http://stackoverflow.com/a/28537958/2293304
// https://github.com/janl/mustache.js/blob/master/mustache.js#L82
var entityMap = {
"&": "&",
"<": "<",
">": ">",
'"': '"',
"'": ''',
"/": '/'
};
return function(str) {
return $sce.trustAsHtml(String(str).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
}));
}
});
There are two separate issues with escaping HTML. The first issue is that entities need to be encoded, and the second issue is that the result needs to be trusted so the data can be used as html bindings. Adding the following code to your controller(s) provides a solution for both issues using the $sce service.
CoffeeScript Solution:
MyApp.controller('MyController', ['$scope','$sce',($scope,$sce) ->
###
...
###
$scope.html5Entities = (value) ->
value.replace(/[\u00A0-\u9999<>\&\'\"]/gim, (i) ->
'&#' + i.charCodeAt(0) + ';'
)
$scope.trustAsHtml = (value) ->
$sce.trustAsHtml(value)
###
...
###
])
Javascript Solution:
MyApp.controller('MyController', ['$scope','$sce', function($scope,$sce) {
/* ... */
$scope.html5Entities = function(value) {
return value.replace(/[\u00A0-\u9999<>\&\'\"]/gim, function(i) {
return '&#' + i.charCodeAt(0) + ';'
})
};
$scope.trustAsHtml = function(value) {
return $sce.trustAsHtml(value);
};
/* ... */
}]);
The first function html5Entities does the actual entity encoding, while the second function trustAsHtml marks a string as safe to use in Angular for HTML bindings. Both versions require that the $sce service be included in your controller.
Example usage:
<div ng-bind-html="trustAsHtml((html5Entities(product.title) | highlight: $select.search))"></div>
See related issues:
Encode html entities in javascript
AngularJS : Insert HTML into view
You can implement filter like this:
app.filter('escape', escape);
function escape() {
return function (html) {
return angular.element('<pre/>').text(html).html();
};
}
There are two ways to do HTML sanitization in AngularJS. The first is by using the ngBindHtml directive and the second by using the $sanitize service.
function MyCtrl ( $scope, $sanitize ) {
$scope.rawHtml = "<div><script></script></div>";
$scope.sanitizedHmtl = $sanitize( $scope.rawHtml );
}
Then these two are functionally equivalent:
<div ng-bind-html="rawHtml"></div>
<div ng-bind-html-unsafe="sanitizedHtml"></div>
If used in a directive, as in your question, you can simply insert the sanitized HTML:
element.html( scope.sanitizedHtml );
But in most cases when writing directives, you'd have this in a template and use ngBindHtml, as above. But it works for the corner cases.
It's not the straight solution, but if you'd dive into angular-sanitize code, you could find function encodeEntities. It's nice but private. Looking for usages of it you'd go to htmlSanitizeWriter, and then to sanitizeText.
It's still private but used in public filter linky.
Either you can explicitly use linky and hope that no links will be found, or reimplement sanitizeText or encodeEntities int your services.
Use [innerHtml] shorthand tag in a HTML template file you are using in your Angular app.
My example shown below to escape HTML generated by wordpress on post_content that is retrieved from my WP API and therefore the html tags will not display in the browser:
<div [innerHtml]="post.post_content" *ngIf="post && post.post_content"></div>
Hope this helps.
You can use encodeEntities() function in ngSanitize to escape & < > etc.
Related
I already know what is the purpose of each item in : compile vs link(pre/post) vs controller
So let's say I have this simple code :
HTML
<body ng-controller="mainController">
{{ message }}
<div otc-dynamic=""></div>
</body>
Controller
app.controller("mainController", function($scope) {
$scope.label = "Please click";
$scope.doSomething = function() {
$scope.message = "Clicked!";
};
});
Directive
app.directive("otcDynamic", function($compile) {
var template = "<button ng-click='doSomething()'>{{label}}</button>";
return {
compile: function(tElement, tAttributes) {
angular.element(tElement).append(template);
for (var i = 0; i < 3; i++) {
angular.element(tElement).append("<br>Repeat " + i + " of {{name}}");
}
return function postLink(scope, element, attrs) {
scope.name = "John";
}
}
}
});
So as we can see , I modify the template (at the compile function - which is where it should be actually)
Result ( plnker):
But
I didn't know that template:... can also take a function.
So I could use the template function instead (plunker) :
app.directive("otcDynamic", function() {
var template1 = "<button ng-click='doSomething()'>{{label}}</button>";
return {
template: function(element, attr) {
element.append(template1);
for (var i = 0; i < 3; i++)
element.append("<br>Repeat " + i + " of {{name}}");
},
link: function(scope, element) {
scope.name = "John";
}
}
});
Question
If so - When should I use the template function vs compile function ?
Let me try to explain what I understood so far.
Directives is a mechanism to work with DOM in Angular. It gives you leverage of playing with DOM element and it's attribute. So it also gives you callbacks to make your work easy.
template , compile and link are those examples. Since your question is specific with compile and template I would like to add about link as well.
A) Template
Like it state, it is a bunch of HTML tags or files to represent it on DOM directly as the face of your directive.
Template can be a file with specific path or inline HTML in code. Like you stated above. template can be wrap in function but the sole use of template is the final set of HTML which will be placed on DOM. Since you have the access to element and its attributes, you can perform as many DOM operation here as well.
B) Compile
Compile is a mechanism in directive which compiles the template HTML or DOM to do certain operation on it and return final set of HTML as template. Like given in Angular DOC
Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
Which clearly says that, this is something on top of template. Now like I said above you can achieve similar operations in template as well but when we have methods for its sole purpose, you should use them for the sake of best practice.
You can read more here https://docs.angularjs.org/api/ng/service/$compile
C) Link
Link is used to register listeners like $watch, $apply etc to link your template with Angular scope so that it will get binded with module. When you place any directive inside controller, the flow of scope goes through the link that means the scope is directly accessible in link. Scope is sole of angular app and thus it gives you advantage of working with actual model. Link is also useful in dom manipulations and can be used to work with any DOM element using jQlite
So collecting all above in one
1. Template is the primary source of DOM or HTML to directive. it can be a file or inline HTML.
2. Compile is the wrapper to compile HTML into final template. It is used to gather all the HTML element and attribute to create template for directive.
3. Link is the listener wrapper for various scope and watchers. It binds scope of current controller with html of template and also do manipulation around it.
Hope this helps a bit to understand. Thanks
I need a filter for AngularJS that takes a string and finds the anchor tags and replaces the href with an:
The input comes from a third party API, is of variable length and could have zero or a thousand instances of tags in.
Sample input would be:
<p ng-html-bind="someScopedVariable | replaceAnchor"></p>
The filter would be:
angular.module('imApp').filter('replaceAnchor', function () {
return function(string) {
if (string) {
/* Sudo code would be:
1. find all <a> in string;
2. get the value of the href attribute and assign to variable hrefHolder
3. replace all href attributes with ng-click="(hrefHolder)";
4. return replaced content; */
} else {
return '';
}
}
})
ng-click="aFunction('original href value in here')";
JQuery is loaded in full (as opposed AngularJS jQLite) so happy if this is JQuery based. Iv tried combinations of $.each, $.find and $.replacewith with no joy so far.
Okay, there's A LOT of work to something like this. Simply replacing the href with "ng-click" will not actually make the "ng-click" magically work. ng-bind-html simply includes the raw html and does not compile it. Using our filter, we can make angular compile the html as well, making it work as expected.
First, here's a working plunkr.
Here's the code:
HTML:
<p ng-bind-html="someScopedVariable | replaceAnchor:'sampleFunction':this"></p>
We're passing some extra variables to the filter. The first one 'sampleFunction' will be the name of the scope function you want to call with the anchors. The second function is the scope you want to compile the html with. this refers to the current scope.
JS:
angular.module('imApp', [])
.config(function($sceProvider) {
$sceProvider.enabled(false);
})
.run(function($rootScope) {
$rootScope.someScopedVariable = 'Google';
$rootScope.sampleFunction = function(href) {
event && event.preventDefault && event.preventDefault();
alert(href);
};
})
.filter('replaceAnchor', function ($rootScope, $compile) {
return function(str, fn, scope) {
scope = scope || $rootScope;
if (str && fn) {
// Create a temporary container that we can use to search for anchor tags
var tempContainer = $('<div/>');
tempContainer.html(str);
// 1. find all <a> in string.
var anchorTags = tempContainer.find('a');
anchorTags.each(function() {
// 2. get the value of the href attribute and assign to variable hrefHolder
var hrefHolder = $(this).attr('href');
// 3. replace all href attributes with ng-click="(hrefHolder)";
$(this).attr('ng-click', fn + '(\'' + hrefHolder + '\')');
});
// 4. return replaced content
return $compile(tempContainer.html())($rootScope);
} else {
return str;
}
}
});
There's some boilerplate there, but I'm leaving it in because it's crucial to understanding what's happening. Let's look at the filter...
Our filter function that we're returning takes three arguments: the original html, the function name, and the scope. If the scope isn't passed, $rootScope will be used. First I create a temporary container to keep things clean and so that we only have one root element. Then I append the passed in original html to that temp container. Then we can use jQuery just like we normally would to loop through all anchor tags, grab their href, and create a new ng-click attribute that calls the function name we passed in with the href as it's only argument. Lastly, we return the new html that has been compiled to make the ng-clicks actually work.
You could always hardcode the function name into the filter, but I like to keep things reuseable.
I have some html data that I'm loading in from a json file.
I am displaying this html data by using ngSanitize in my app and using ng-bind-html.
Now I would like to convert any links in the json blob from the standard
link
to:
<a ng-click="GotoLink('some_link','_system')">link</a>.
So I'm doing some regExp on the json file to convert the links, but for some reason however ng-bind-html is filtering out the ng-click in it's output, and I can't figure out why. Is it supposed to do this, and if so is it possible to disable this behavior?
Check out this jsFiddle for a demonstration:
http://jsfiddle.net/7k8xJ/1/
Any ideas?
Ok, so the issue is that it isn't compiling the html you include (angular isn't parsing it to find directives and whatnot). Can't think of a way to make it to compile from within the controller, but you could create a directive that includes the content, and compiles it.
So you would change
<p ng-bind-html="name"></p>
to
<p compile="name"></p>
And then for the js:
var myApp = angular.module('myApp', ['ngSanitize']);
angular.module('myApp')
.directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
)};
}]).controller('MyCtrl', function($scope) {
var str = 'hello http://www.cnn.com';
var urlRegEx = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+#)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+#)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-]*)?\??(?:[\-\+=&;%#\.\w]*)#?(?:[\.\!\/\\\w]*))?)/g;
result = str.replace(urlRegEx, "<a ng-click=\"GotoLink('$1',\'_system\')\">$1</a>");
$scope.GotoLink = function() { alert(); }
$scope.name = result;
});
Angular 1.2.12: http://jsfiddle.net/7k8xJ/4/
Angular 1.4.3: http://jsfiddle.net/5g6z58yy/ (same code as before, but some people were saying it doesn't work on 1.4.*)
I still faced some issue with the compile, as that was not fulfilling my requirement. So, there is this, a really nice and easy hack to work around this problem.
We replace the ng-click with onClick as onClick works. Then we write a javascript function and call that on onClick event.
In the onClick function, we find the scope of the anchor tag and call that required function explicitly.
Below is how its done :)
Earlier,
<a id="myAnchor" ng-click="myControllerFunction()" href="something">
Now,
<a id="myAnchor" onClick="tempFunction()" href="something">
at the bottom or somewhere,
<script>
function tempFunction() {
var scope = angular.element(document.getElementById('myAnchor')).scope();
scope.$apply(function() {
scope.myControllerFunction();
});
}
</script>
This should work now. Hope that helps someone :)
For more info, see here.
Explicitly Trusting HTML With $sce
When you want Angular to render model data as HTML with no questions asked, the $sce service is what you’ll need. $sce is the Strict Contextual Escaping service – a fancy name for a service that can wrap an HTML string with an object that tells the rest of Angular the HTML is trusted to render anywhere.
In the following version of the controller, the code asks for the $sce service and uses the service to transform the array of links into an array of trusted HTML objects using $sce.trustAsHtml.
app.controller('XYZController', function ($scope, $sce) {
$sce.trustAsHtml("<table><tr><td><a onclick='DeleteTaskType();' href='#workplan'>Delete</a></td></tr></table>");
I am seeing a strange behaviour when using angular directive.
In the code below
HTML
<body ng-app="loadTweetsModule">
<div tweets> Load </div>
<div loadTweets> loadTweets </div>
</body>
Javascript
var loadTweetsModule = angular.module("loadTweetsModule",[]);
loadTweetsModule.directive('tweets',function(){
return {
link : function(scope,element){
element.bind("mouseenter", function(){
console.log("tweets");
});
}
};
});
loadTweetsModule.directive('loadTweets',function(){
return {
link : function(scope,element){
element.bind("mouseenter", function(){
console.log("loadTweets");
});
}
};
});
JSFiddle Link
The two directive loadTweets and tweets are the same except for the name. The directive tweets works as expected but loadTweets does not work. I am not able to find out the reason for this behaviour. Can somebody explain this?
if you write in js loadTweets you should write in html load-tweets
from http://docs.angularjs.org/guide/directive
Directives have camel cased names such as ngBind. The directive can be
invoked by translating the camel case name into snake case with these
special characters :, -, or _. Optionally the directive can be
prefixed with x-, or data- to make it HTML validator compliant. Here
is a list of some of the possible directive names: ng:bind, ng-bind,
ng_bind, x-ng-bind and data-ng-bind.
So as of Angular 1.1.4, you can have a dynamic template url. From here,
templateUrl - Same as template but the template is loaded from the specified URL. Because the template loading is asynchronous the compilation/linking is suspended until the template is loaded.
You can specify templateUrl as a string representing the URL or as a function which takes two arguments tElement and tAttrs (described in the compile function api below) and returns a string value representing the url.
How can I utilize this to generate a dynamic template based on, say, an attribute on my directive? Obviously this doesn't work, since tAttrs.templateType is simply the string "templateType"
templateUrl: function (tElement, tAttrs) {
if (tAttrs.templateType == 'search') {
return '/b/js/vendor/angular-ui/template/typeahead/typeahead.html'
} else {
return '/b/js/vendor/angular-ui/template/typeahead/typeahead2.html'
}
}
Given that I don't have access to the scope, how do I manage this?
The following is also possible for creating dynamic templates in AngularJS:
In your directive use:
template : '<div ng-include="getTemplateUrl()"></div>'
Now your controller may decide which template to use:
$scope.getTemplateUrl = function() {
return '/template/angular/search';
};
Because you have access to your scope parameters, you could also do:
$scope.getTemplateUrl = function() {
return '/template/angular/search/' + $scope.query;
};
So your server could create a dynamic template for you.
templateUrl: function (elem, attrs) {
return attrs["template"] == "table" ?
"tableTemplate.html" : "itemTemplate.html";
}
So the issue was with how I hacked the typeahead directive ... I was setting a scope variable on the typeahead, to be evaluated on the typeaheadPopup directive. Instead, I just passed the templateType attr directly as string & evaluated that. E.g.
var popUpEl = angular.element(
"<typeahead-popup " +
"matches='matches' " +
"active='activeIdx' " +
"select='select(activeIdx)' " +
"template-type='" + attrs.templateType + "'" +
"query='query' " +
"position='position'>" +
"</typeahead-popup>");
Instead of "template-type='templateType'"
Ran into a similar issue when creating a file upload fallback for browsers that don't support the File API (< IE10). Key difference is I needed the page to intelligently decide which template to display without the benefit of an attribute value to switch on.
I ended up using the constant provider for my directive. Constants basically set up default parameters that can be injected anywhere in your directive. I simply let the constant call a function to determine browser support, then reference that value when I need to determine which template to pull. This is nice since 1) there's no attribute to reference and 2) it's available during the pre-link phase when you don't have access to the controller.
(function () {
var myDir = angular.module('myDir', []);
myDir.constant('myDirConfig', {
hasFileSupport: fileApiIsSupported()
});
myDir.directive('myDir', ['myDirConfig', function (myDirConfig) {
return {
templateUrl: function () {
if (myDirConfig.hasFileSupport) {
return 'pathToTemplate/html5.html';
} else {
return 'pathToTemplate/fallback.html';
}
}
};
}];
function fileApiIsSupported() { return (...); }
})();