$compile link function apparently not applying scope - javascript

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);
});
}
};
})

Related

AngularJS and Sanitize - Sanitize HTML without ngBind Directive

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.

Knockout.js: how to make a variable available to all viewmodels so it can be used in bindings without explicitly putting it in all viewmodels?

I am refactoring code base based on Knockout.js and have stumped into a problem.
The code uses templates as interpolated strings in typescript eg.
var template = `<div title='${Resources.string1}'> ${Resources.string1} </div>`
The problem is that the strings can contain a single quote which sometimes break the proper HTML parsing after it has been compiled to javascript.
var template = `<div title='I have a ' single quote'> I have a ' single quote</div>`
I want to expose the Resources object so that it is visible in all the view models without explicitly adding the resource object to every view model so that I can use it like this
<div data-bind="title: Resource.string1"> ${Resources.string1} </div>
Or is their anything global to which I can bind this resources object and access it from all view models inside my app.
You can refer to a global variable without any problem.
This one works, here is a demo fiddle.
var Resources = {
string1: "something here with ' quote"
}
window.Resources = Resources;
ko.applyBindings({});
<div data-bind="text: Resources.string1">
</div>
However, this feels like kind of a hack. I'd rather use some HTML encoding for the resources. See some possibilities here. I'd add a getResource function which would return the requested resource in a properly encoded form.

Accessing scope params of children directives

I'm new in AngularJS, using it for two months in a project. I've learned how to use directives and theirs scopes (false, true, obj literal), but there's some questions about it...
First of all, we have some ng-repeats and directives with some behaviors, I tried to present a equivalent scenario in this link.
I didn't figure out how to access a function (testfn - within ng-controller) inside a directive child of another directive myItemDirective. But in myStepDirective it's accessible, I tried to pass it like in the first "layer" but didn't work.
PS.1: I created a myStepDirective with a isolated scope for other examples, if you need, just uncomment to test. Both I got a way to access params/functions from parent (controller), but not inside a grandchild.
Why directive's scope params doesn't work with camel case params? I don't remember to read some hint in AngularJS docs... typewithnocase inside myItemDirective works but typeList not.
Thanks!
EDITED]
For your 1. Here is a working fiddle working with limited scope and camel to snake case conversion : https://jsfiddle.net/wu0avqau/
I spend a loooooong time not understanding why it didn't worked but you juste forgot a = in your ng click inside your second directive
ng-click"testfn()"
For your 2. I can refer you to the documentation : https://docs.angularjs.org/guide/directive
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."
basicaly your myItemDirective would be my-item-directive inside your template but still be myItemDirective inside your js.
Good luck,
Thibaud Lamarche

AngularJS Directive - re-run link function on scope parameter change

I have a directive that builds a set of nested <ul> elements representing a folder structure. I used the link function to create the new DOM elements and append them to the directive instance element:
function link(scope, iElement, iAttr) {
var rootElement = buildChildElement(scope.tree);
iElement.append(rootElement);
}
Elements within the <ul> tree are wired with jQueryUI's drag/drop interactions that call a function on the Controller housing the directive to update the scope parameter based on the drag & drop events.
I would like the <ul> tree to automatically update when there is a change to the scope parameter. I have tried a watch function within my link function:
scope.$watch('tree', function(newTree, oldTree) {
var newRoot = buildChildElement(newTree);
iElement.contents().remove();
iElement.append(newRoot);
}
This works to a certain extent, but the call to remove() fires off the $watch() method a second time which ends up reverting my Controller changes. If I comment out the remove(), I can see that a new <ul> tree is written that properly reflects the changes to the parameter made in the Controller.
The double firing $watch() makes me think I'm going about this wrong. Without it, my parameter is properly updating but my <ul> doesn't update (the dropped element stays where it was dropped).
What's the correct way to make sure your directive is refreshed on a change in one of the scope parameters?
Should I be using the compile function and building the <ul> tree based on the attributes array instead of using the link function?
Your approach is very jQuery-style. I think you'll find that you're working against Angular in this case. sh0ber is right with his/her question; you should post a demo or something, or at least some sample code so you can have an effective answer.
I think you want to make a recursive tree directive. Check out this SO answer for some interesting approaches to this. The main idea is that watch is unnecessary. Simply change the object and Angular will take care of the rest. The most efficient thing is to change the specific node objects directly rather than replacing the whole object, but that will work too.
scope.$watch('tree', function(newTree, oldTree) {
var newRoot = buildChildElement(newTree);
iElement.contents().remove();
iElement.append(newRoot);
},**true**)
I think you can have a try and reference the watch API for more information
Here is another artical
http://www.bennadel.com/blog/2566-scope-watch-vs-watchcollection-in-angularjs.htm

AngularJS Create new scope in template?

Using Symfony2.x, I have a twig loop going through data for some data, and I also have an ng-repeat going on for similar elements (difference being these ones get loaded in the background though), but both are to share the same functionality.
I have some odd functionality going on in the twig loop versions that are working perfectly fine in the ng-repeat versions. I have a feeling it's simply a scope issue.
I read in the docs that ng-repeat will automatically create a new scope for the repeated elements, but of course this doesn't happen with a twig loop.
How does one manually, and preferably exclusively IN the template, invoke a new scope per repeated element?
The easiest way might be to add a directive to each element. This can be done in the template. The directive can then request new scope (via scope:true or scope:{}) and each repeated element will get a new scope associated with it.
You can create a directive on an element like:
<div mydirective></div>
Then in your code, define the directive:
myApp.directive('mydirective',function(){
return {
scope: true,
link: function(scope, elem, attrs){
// do some scope / element stuff here
}
}
});

Categories

Resources