I have a page that I am using ng-include to pull in a file as rendered HTML. I would like to include that same file as code, next to it.
Is there a way to do this in Angular, or jQuery? I want to be able to include HTML, CSS and potentially JS files as code that would be run through a code-renderer, like Prism or something similar.
This is similar to jsfiddle or codepen, in that you can see the code and rendered view in the same window, but I do not need them to be connected (ie. edit the code to see the rendered result.) I just want to pull both views from the same file.
I am currently using an Angular directive to loop through a JSON file and push out blocks of HTML according to what is listed in the JSON. My directive is:
app.directive('patterns', function() {
return {
restrict: 'E',
require: ['^ngType', 'ngTemplate'],
scope: {
ngType: '#',
ngTemplate: '#'
},
templateUrl: 'patterns.html',
controller: function($scope, $http, $sanitize) {
var theType = $scope.ngType;
$http.get('indexes/json/' + theType + '.json')
.then(function(result) {
$scope.patterns = result.data;
});
},
controllerAs: 'patterns',
link: function(scope, iElement, iAttrs, ctrl) {
scope.getType(iAttrs.ngType);
}
}
});
And I want to add something that also uses pattern.anchor (based off an "anchor" key I have in my JSON) to grab a particular file and output only the code. I can currently use pattern.anchor with ng-include to output rendered HTML, but not only code.
Yes, with angular you can use ngSanitize or the $sanitize service.
There's a simple example available here: http://codepen.io/tipsoftheday/pen/jthks
angular.module('expressionsEscaping', ['ngSanitize'])
.controller('ExpressionsEscapingCtrl', function ($scope, $sanitize) {
$scope.msg = 'Hello, <b>World</b>!';
$scope.safeMsg = $sanitize($scope.msg);
});
and a more complex example available in the Angular documentation here: https://docs.angularjs.org/api/ngSanitize/service/$sanitize.
Ampersand-Delineated Character Entity
In the code, replace all angle brackets < and > with a character entity code < and >. The entity codes get rendered as angle brackets but don't get processed as such. Unfortunately, this doesn't preserve the formatting of your file (read on to see how to do that), since all whitespace gets compressed to a single non-breaking space.
<strong> The strong tag is shown rather
than rendered, but all whitespace characters
still get compressed into a single space.
However, this <br /> break tag gets rendered rather
than shown. </strong>
HTML provides a block-level element called pre. Anything inside the pre tag is considered pre-rendered and the browser displays it as-is. Whitespace in this block does not get condensed. This will preserve the formatting of your code.
<pre>
<strong> In this block, if I add extra spaces, they are shown.
If I move to a newline, that is shown as well.
HTML tags still need to have<br />their angle brackets replaced
in order to be shown rather than rendered.</strong>
</pre>
If you are using JavaScript/AJAX to include the file, you can first do a string replace function to replace the angle brackets with the character entity codes. If you are doing server-side includes, you can do something similar with your server-side language of choice.
If you want to do all of this automatically, Mike Feltman suggested a method using Angular.
Related
Lets supose we need to sanitize an HTML string and we can't use ng-bind-html directive, for example:
<span data-toggle="tooltip" title="Edit {{customer.name}}">Text</span>
If we have special chars in customer.name this line would be printed as the html version like é and we want é instead.
I have tested with:
$sce.trustAsHtml(customer.name)
$sce.parseAsHtml(customer.name)
But nothing can "translate" this html. How can be this done?
A short explanation would be: how to sanitize html inside a directive (not in the body with ng-bind-html).
It doesn't have to be so complicated.
Instead, use setAttribute and textContent (V.S. innerHTML) on elements, and browsers themselves will take care of sanitizing for you.
// To set element attributes
$span.setAttribute("title", "Edit" + customer.name);
// To set element content
$span.textContent = customer.name;
For more details, see the post here. These are one time bindings of course, so if you need updates, just throw $watch in the midst.
From an oficial documentation:
ngBindHtml uses $sce.parseAsHtml(binding expression). Here's the actual code (slightly simplified):
var ngBindHtmlDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
element.html(value || '');
});
};
}];
so I think all you need is $sce.parseAsHtml (https://docs.angularjs.org/api/ng/service/$sce#parseAsHtml).
If you are not able to persuade angular to print HTML anyway, you can try to use
customer.name.replace(/é/g, String.fromCharCode(233));
You can find some basic codes here: http://www.javascripter.net/faq/accentedcharacters.htm
That should work, but it is not definitely the best solution. You should always use ng-bind-html.
I noticed something very strange about Angular 1.5.6 components. I have a component called scale. I call it:
<scale x-scale="xScale"></scale>
And in my controller:
$scope.xScale = 'lin'.
And my component definition:
angular
.module('myapp')
.component('scale', {
templateUrl: 'analyse/components/scales/scale.tpl.html',
controller: function(){
console.log('in controller and this is ', this);
},
bindings: {
xScale: '='
},
});
The console log outputs undefined.
But if i change x-scale to r-scale in my template and xScale in the binding to rScale, suddenly it works. In fact it seems that if i replace the x with any other letter, it works. Why is this?
It's in the documentation for directives
Normalization
Angular normalizes an element's tag and attribute name
to determine which elements match which directives.
We typically refer to directives by their case-sensitive camelCase normalized name (e.g. ngModel).
However, since HTML is case-insensitive, we refer to
directives in the DOM by lower-case forms, typically using
dash-delimited attributes on DOM elements (e.g. ng-model).
The normalization process is as follows:
Strip x- and data- from the front of the element/attributes.
Convert the :, -, or _ -delimited name to camelCase.
So Angular strips of x- from the front of any attribute name to normalize it, this is done because both regular data-attributes, starting with data-, and x-attributes, starting with x- is valid in HTML 5.
The HTML5 specification states that
Attribute names beginning with the two characters "x-" are reserved
for user agent use and are guaranteed to never be formally added to
the HTML language.
It also states that
For markup-level features that are intended for use with the HTML
syntax, extensions should be limited to new attributes of the form
"x-vendor-feature", where vendor is a short string that identifies the
vendor responsible for the extension, and feature is the name of the
feature.
The x- attributes aren't used very often, but as noted above they are reserved for browser vendors, and you shouldn't be using them, instead you should be using data-attributes, where incidentally, Angular will also remove the data- part for you, so these
<scale data-scale="scale"></scale>
<scale x-scale="scale"></scale>
<scale scale="scale"></scale>
are all the "same" when you do
$scope.scale = 'lin'.
I'm trying to compile some HTML on my controller like so:
$scope.online = networkService.isOnline();
var wrapper = angular.element(document.createElement("i"));
var compiledContents = $compile($scope.chapter.contents);
var linkedContents = compiledContents($scope);
wrapper.append(linkedContents);
$scope.chapter.linkedContents = wrapper.html();
Where the HTML being compiled has a few elements with a ng-if='online' there. But the compiled HTML always comes out with those elements commented, as if online was not true (which it is - I even got to the point where I added a console.log(scope.online) in Angular's $compile function and it printed true).
Am I missing anything here?
wrapper.html() gives back a string representing the inner HTML of the element. You are then assigning that string to $scope.chapter.linkedContents. Although it is not clear from your question how and where you are actually using $scope.chapter.linkedContents, one thing is certain - this string is definitely not "linked" in any way.
The actual "linkedContents" are the elements that should end up in the DOM. In your case, it is the wrapper element with its contents, but again - unclear, how, if ever, it ends up in the DOM.
But you shouldn't even be dealing with DOM in a controller. Controllers are DOM agnostic, so right there you should see a big warning sign that you are doing something wrong. Make sure that you understand the role of a controller, a directive, etc...
I think I understand what the problem you are trying to solve. You get some dynamic uncompiled HTML (or actual elements) - i.e. $scope.chapter.contents and you need to have it placed in the DOM and compiled/linked.
Typically, to bind HTML one would use ng-bind-html (assuming it's either trusted or sanitization is on):
<div ng-bind-html="chapter.contents">
</div>
But this would not be $compiled. To compile, I'd suggest writing your own directive that would work similar to ng-bind-html, but would also compile it:
<div compile-html="chapter.contents">
</div>
Then, the directive would take the content, compile/link it against some scope (say, child scope) and append it to the element hosting the directive compileHtml.
.directive("compileHTML", function($compile, $parse){
return {
scope: true,
link: function(scope, element, attrs){
// get the HTML content
var html = $parse(attrs.compileHtml)(scope);
element.empty();
// DISCLAIMER: I'm not dealing with sanitization here,
// but you should keep it in mind
$compile(html)(scope, function cloneAttachFn(prelinkContent){
element.append(prelinkContent);
});
}
};
})
as Web Components, I mean AngularJs's directives here.
I am trying to use special characters as a tag's name, especially asiatic ones (korean even more specifically).
Here's a plunker so you'll get a better grasp on what I try to achieve.
// library
(function (angular) {
angular.module('molecules', [])
.directive('헐', function () { return {
template: 'ㅎㅓㄹ'
}});
})(window.angular);
// main module
(function (angular) {
angular.module('lab', ['molecules']);
})(window.angular);
<div ng-controller="monitor1">
<헐></헐>
</div>
It seems to have to do with how the browser is interpreting the DOM. If you append an alphabet character to the foreign character (in the directive and the start and end tags), it works as expected i.e.
<a헐></a헐>
Note - check out the DOM while you are at it (I was checking in IE11) - notice that the tag is closed with . With <헐> notice that the IE has taken it upon itself to make some modifications. There is also the console error HTML1407: Invalid tag name. First character should match [a-zA-Z].
This filter is operating on a field that's being loaded from a REST service. It's converting new line characters to <br/> tags and they're getting escaped.
angular.module('myMod').filter('convertBreaks', function($sce) {
return function(input) {
return $sce.trustAsHtml(input.replace(/[\n]/g, "<br/>"));
};
});
It's called like this:
<p>{{data.x.y.z | convertBreaks}}</p>
The <br/> tags are coming out escaped. Any suggestions why?
Update
Forgot to mention that this is being executed by a directive that uses the $compile service.
Try using ng-bind-html
<p ng-bind-html="data.x.y.z | convertBreaks"></p>
Angular has some built in security mechanisms that will cause those kinds of shenanigans.