Angular JS - Evaluation Timing - javascript

I have the following problem in angularjs. I want to use an UI libary that injects some html code itself (Metro UI CSS) and I have troubles to getting the execution order right.
A simple example: http://metroui.org.ua/hint.html
If I declare in html:
<span data-hint="My hint"></span>
The UIjs will create the html elements needed for the hint display. Nofurther script code has to be added. Well actually when you load the js the following code gets executed: $('[data-hint]').hint();
Since the angular created html doesn't exist when I load the javascript, it doesn't work at first at all.
I believe I need an angular directive to solve the problem (and in parts it does) - I created the fowlling directive:
app.directive('hints', function() {
return {
link: function() {$('[data-hint]').hint()}
};
});
The following does work, even if this is in html created by angular:
<span hints data-hint="the text for my hint">test</span>
The following doesn't work (at least it doesn't behave the way I'd like to):
<span hints data-hint="{{something}}">Test</span>
The hint text will display literally {{something}} and not whatever is behind the angular expression. I tried already to create template like, but the result is still the same:
app.directive('hints', function() {
return {
template: '<span data-hint="{{something}}">Test</span>',
link: function() {$('[data-hint]').hint()}
};
});
Any hints on how to solve that problem would be greatly appreciated.

The main problem seems to be that if you attach the hint() in the link function, jquery takes the old value before angular has evaluated it. One option would be to wrap $timeout(function(){..}) around element.hint(), but I use that hack too much already, and it doesn't solve another problem: the hint needs to update when the $scope changes (if it depends on the $scope). To solve that problem we can add a $watch function and update the hint value when needed.
So, in conclusion:
/* This directive triggers automatically on data-hint as well */
app.directive('hint', function($timeout) {
return {
link: function(scope, element, arguments) {
/* set the value of the hint, then attach the metro-hint widget */
element.data('hint' , arguments.hint).hint();
/* watch for changes in the value so the hint gets updated */
scope.$watch(function(){
return arguments.hint;
}, function() {
element.data('hint' , arguments.hint);
});
}
};
});
(Tested with jquery 1.10.2, jquery-ui 1.10.3 and angular 1.2.6)

Related

onsenui cannot change content

I’m using onsen ui version 1, followed https://onsen.io/v1/guide.html to make changes to DOM
This section to be exact
// Add another Onsen UI element
var content = document.getElementById("my-content");
content.innerHTML="<ons-button>Another Button</ons-button>";
ons.compile(content);
The problem is nothing changed on the page.
If i dump “content” variable or dump the HTML element it shows the newly edited version on browser console. but on page still the old one.
ons object is instantiated, compile method is callable, tried different HTML elements.
Either you are doing something incorrectly or it's an angular refresh issue.
For the first scenario it's easier if you provide a codepen, so that we can see the problem. Currently the code you are mentioning is working fine for me here.
For the second scenario actually the third line ons.compile(content); should remove this problem imo, it might be a version issue, or there is some context which I am missing.
If you're doing something angular related then you should also show where you are calling these 3 lines from. In order to work it should be called from something like an ng- event (for example ng-click).
JS:
app.controller('yourControllerName', function($scope) {
$scope.addButton = function () {
var content = document.getElementById("my-content");
content.innerHTML="<ons-button>Another Button</ons-button>";
ons.compile(content);
}
});
HTML:
<ons-button ng-click="addButton()">Add another button</ons-button>
Finally if you are unable to make it work you can do something like
app.controller('yourControllerName', function($scope) {
$scope.addButton = function () {
var content = document.getElementById("my-content");
content.innerHTML="<ons-button>Another Button</ons-button>";
ons.compile(content);
$scope.apply(); // should fix the issue, but not recommended
}
});
PS: You can also try out onsen 2 - there you can:
experiment with the interactive tutorial
try out the vanilla version (without any external framework) - which will not have issues like this one

After using $sce.trustAsHtml, ng-click not working

I am trying to print to the screen custom html using angular. I am using $sce.trustAsHtml in combination with ng-bind-html to accomplish this. The goal is not only to be able to print this custom html, but that it will retain directives such as ng-click and they will be usuable. Examples I have seen in articles such as follows are promising:
AngularJS render HTML within double curly brace notation
However in my implementation I find that although the html renders correctly including references to ng-click, the directive doesn't seem to work anymore when trying to click on the link I am using it on; here is some sample code:
$scope.htmlExpression = $sce.trustAsHtml("<a ng-click='test();'>Click Me</a>");
$scope.test = function() {
console.log('Hello World!');
}
<div>
<p ng-bind-html="htmlExpression"></p>
</div>
As everything renders fine and nothing appears lost in translation when analyzing the source; I am left feeling as if I have left something out. Any help is appreciated.
Use https://docs.angularjs.org/api/ngSanitize and bind the html. If this does not work, $digest to reboot the digest cycle.

AngularJS custom directive loosing root properties

I have created a sidebar custom directive. It's working properly as it loads on where it should. What isn't working properly are the tags. Their supposed behavior is that of a drowpdown, where when clicked they show their inner elements. It works properly when the code is pasted directly but not when the directive is called with the code inside the other html file. I took 2 screenshots to show the difference between using a class="page-sidebar" inside the file that contains the html code of the directive and using it on the "root" file:
It's pretty clear that several properties on the highlighted lines are not being applied on the first one.
Please help as I need this as a "partial" view to be used across several pages.
EDIT: Directive code:
app.directive('sidebar', function () {
return {
restrict: 'E',
templateUrl: "/app/views/sidebar.html"
};
});
EDIT2:
Adding this in the post because it might be confusing from how I explained it:
I see where the confusion might be but they're different things. < sidebar > is a directive created by me. class="page-sidebar" is from the template I'm using and is what formats everything to its place. I tried to insert the class="page-sidebar" into the directive to see if it would work, but they're different things.
EDIT3:
To clear up the confusion, I hope: both pics show the sidebar is working. I know it's an element and as such I'm using < sidebar >, it's working, this is not the problem. The problem is when I use it, the contents such as Dropdowns (as shown in the second pic) don't work when I click them, while when the element contents are simply pasted into the index.html and not in the sidebar.html, it works.
EDIT4:
Found the issue but still no solution. I changed some stuff up and instead of the sidebar it's now on the widgets. Sidebar is now always loaded and it's the page contents which are loaded depending on the URL. This helped me track down the issue:
$(".owl-carousel").owlCarousel({mouseDrag: false, touchDrag: true, slideSpeed: 300, paginationSpeed: 400, singleItem: true, navigation: false,autoPlay: true});
The previous code is in a plugins.js file which is included in the html. For some reason, this line is NOT being run when the page is loaded. When I ran this line in the chrome console, the proper widget appeared.
For some reason, the js contents are not being run when the page loads.
Your main problem is the restrict: 'E',, which is restricting it to elements. This explains why it works for <sidebar>, but not for <div class="sidebar">. If you want to use classes, you need to change it to restrict: 'C'.
Another problem is that when you are trying to use the directive as a class, you are using class="page-sidebar" rather than class="sidebar".
See the docs for directives.
From angular documentation:
The restrict option is typically set to:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
'M' - only matches comment
These restrictions can all be combined as needed:
'AEC' - matches either attribute or element or class name
The directive definition object for your sidebar quite clearly states that it will treat a DOM node with the tag name sidebar to render the directive template due to the restrict : 'E' property.
So use the directive as an HTML node, and NOT in a class (as it would require the property restrict to be set to C letter).
<sidebar></sidebar>

Compile Angular directive from dynamic html

I can't seem to get this done: I have HTML that is compiled from a ng-repeat, and I'd like to compile the result of that as well. How would I go about that?
I have a dataset containing chunks of text, that have been given a display type. This type is set as the span class. Most types are just triggering CSS rules (for example, comment-style boxes, see screenshot), but others should invoke a directive.
For example, the chunk containing 'named Nicodemus, ' is of type-hidden. I have a directive that collapses the chunk and inserts a little button to expand it.
Code:
<span class="chunk type-{{chunk.type}}" ng-repeat="chunk in verse.chunks">{{chunk.text}}</span>
Results in something like
<span class="chunk type-hidden">named Nicodemus, </span>
If the second would be my source html, it would compile the typeHidden directive just fine. I guess I need to find a way to make angular compile a second time. I can't seem to get it done using $compile (though I guess I don't really understand how that works).
Hope you can help!
Thanks in advance!
Here's a plunker to show how you can get a directive to compile an element and then again.
The code for the lazy:
angular
.module('App')
.directive('compileTwice', compileTwiceFactory);
function compileTwiceFactory($compile) {
return {
restrict: 'AE', // Whatever you want
terminal: true, // Angular should not keep compiling the element
// by itself after encountering this directive!
compile: compile, // Instead, we tell Angular how to compile the rest of the element
priority: 1001, // This directive should get compiled before the others, obviously
};
function compile(element, attrs) {
element.removeAttr('compile-twice');
element.removeAttr('data-compile-twice');
return function postLink(scope, _element, _attrs) {
var compiledTwice = $compile($compile(_element)(scope)[0])(scope)[0];
// do something with compiledTwice
};
}
}
edit: And obviously you can generalize that to compile an arbitrary number of times that you could specify like this:
<div compile-n-times="420"></div>
edit: The plunker doesn't seem to work under Firefox?
I've actually been able to fix this with a workaround. Not as elegant, but it works if I nest my directive within the ngrepeat and hardcode the name, making it visible using ng-if.
<!-- special type hidden -->
<span ng-if="chunk.type=='hidden'">
<span class="type-hidden">
{{chunk.text}}
</span>
</span>

Using Angular, how can I show a DOM element only if its ID matches a scope variable?

I am relatively new to AngularJS.
I have a series of DIVs in a partial view. Each of the DIVs has a unique ID. I want to show / hide these DIVs based on a scope value (that matches one of the unique ID).
I can successfully write out the scope value in the view using something like {{showdivwithid}}
What would be the cleanest way to hide all the sibling divs that dont have an ID of {{showdivwithid}}
I think you are approaching the problem with a jQuery mindset.
Easiest solution is to not use the id of each div and use ngIf.
<div ng-if="showdivwithid==='firstDiv'">content here</div>
<div ng-if="showdivwithid==='secondDiv'">content here</div>
<div ng-if="showdivwithid==='thirdDiv'">content here</div>
If you don't mind the other elements to appear in the DOM, you can replace ng-if with ng-show.
Alternatively use a little directive like this:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {},
template: '<div ng-transclude></div>',
link: function (scope, element, atts) {
if(atts.id != atts.keepIfId){
element.remove();
}
}
};
});
HTML
<div id="el1" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el2" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el3" keep-if-id="{{showdivwithid}}">content here</div>
First, I want to echo #david004's answer, this is almost certainly not the correct way to solve an AngularJS problem. You can think of it this way: you are trying to make decisions on what to show based on something in the view (the id of an element), rather than the model, as Angular encourages as an MVC framework.
However, if you disagree and believe you have a legitimate use case for this functionality, then there is a way to do this that will work even if you change the id that you wish to view. The limitation with #david004's approach is that unless showdivwithid is set by the time the directive's link function runs, it won't work. And if the property on the scope changes later, the DOM will not update at all correctly.
So here is a similar but different directive approach that will give you conditional hiding of an element based on its id, and will update if the keep-if-id attribute value changes:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {
keepIfId: '#'
},
template: '<div ng-transclude ng-if="idMatches"></div>',
link: function (scope, element, atts) {
scope.idMatches = false;
scope.$watch('keepIfId', function (id) {
scope.idMatches = atts.id === id;
});
}
};
});
Here is the Plunkr to see it in action.
Update: Why your directives aren't working
As mentioned in the comments on #david004's answer, you are definitely doing things in the wrong way (for AngularJS) by trying to create your article markup in blog.js using jQuery. You should instead be querying for the XML data in BlogController and populating a property on the scope with the results (in JSON/JS format) as an array. Then you use ng-repeat in your markup to repeat the markup for each item in the array.
However, if you must just "get it working", and with full knowledge that you are doing a hacky thing, and that the people who have to maintain your code may hate you for it, then know the following: AngularJS directives do not work until the markup is compiled (using the $compile service).
Compilation happens automatically for you if you use AngularJS the expected, correct way. For example, when using ng-view, after it loads the HTML for the view, it compiles it.
But since you are going "behind Angular's back" and adding DOM without telling it, it has no idea it needs to compile your new markup.
However, you can tell it to do so in your jQuery code (again, if you must).
First, get a reference to the $compile service from the AngularJS dependency injector, $injector:
var $compile = angular.element(document.body).injector().get('$compile');
Next, get the correct scope for the place in the DOM where you are adding these nodes:
var scope = angular.element('.blog-main').scope();
Finally, call $compile for each item, passing in the item markup and the scope:
var compiledNode = $compile(itm)(scope);
This gives you back a compiled node that you should be able to insert into the DOM correctly:
$('.blog-main').append(compiledNode);
Note: I am not 100% sure you can compile before inserting into the DOM like this.
So your final $.each() in blog.js should be something like:
var $compile = angular.element(document.body).injector().get('$compile'),
scope = angular.element('.blog-main').scope();
$.each(items, function(idx, itm) {
var compiledNode = $compile(itm)(scope);
$('.blog-main').append(compiledNode);
compiledNode.readmore();
});

Categories

Resources