Angular: how to force a recompile of a block - angularjs

I am post-processing the output of prettify to highlight some lines in the code. I'm using code like this, which works fine:
x = angular.element('.prettify li:nth-child(' + zz['line'] + ')');
x.css('background-color', 'yellow');
x.prop('title', zz['message']);
Now, instead of using the title tag to show a message on the line, I want to use Bootstrap tooltip. The obvious change to the above code is:
x.prop('tooltip', zz['message']);
However, this doesn't work. I presume I need to tell Angular to recompile the block, so it picks up the directive for tooltop (hence the title of the question).
Update - here is a fiddle showing what I am trying to do: http://jsfiddle.net/6Y4d9/

to recompile thr block -
you should use $compile service, like so:
$compile(block)(scope)
BUT, for your task your need just:
scope.$apply()
And:
change x.prop to x.attr
http://jsfiddle.net/6Y4d9/1/

If you ever need to force angular to re-digest a certain scope, all you have to do is call:
$scope.$apply();
This will for the $scope to re-apply any changes that may have occurred since the last time it applied. These types of applies only need to be run when the initial event that created the interaction because from an event-handler that is outside of the angular app.
Another option you have is to call:
$scope.$apply(function(){
//put your code in here, and it will run your code and then apply it to the current scope
});

Related

Is there a way to improve dynmically created html compile?

so i have a situation where in i'm creating html using jquery. I have no choice in this matter since I'm forced to use an old jquery plugin and integrate the created html in angular and the only way to do that is $compile. basically the flow is
var createbody = function(htmlcontents,scope){
$compile(htmlcontents)(scope);
}
the company internal jquery plugin is some sort of endless scroll for a table where it destroys and recreates a chunk of the tr's depending on the scroll position. so everytime you scroll, if the plugin needs to destroy and add tr's, createbody gets called.
the problem is that the scroll gets really laggy whenever it does the destroy and create part because of the compile. a directive is not an option at this point.
question: is there a way to cache the previously compiled chunk and use that later on whenever the plugin decides it needs to use that chunk again.? thanks
You can reuse compiled template:
var compiledTemplate = $compile(html);
compiledTemplate($scope1);
compiledTemplate($scope2);
compiledTemplate($scope3);
You can also see how ngRepeat reuses elements: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L469, but it's a bit more complicated.

"$watch"ing a service doesn't appear to update reliably

I am currently building a simple drag & drop directive so that I can move some SVG stuff around on the screen. At the moment I'm still in the early stages, but I have run into a strange issue with $watch that I'm hoping someone can help me with.
I have a service that maintains my mouse state. At the moment it's just the x and y coordinates of the cursor. I also have an attribute level directive that interacts with this service in order to bind to the mouse-move event and update the service whenever someone moves the mouse around. These two items work together like a champ. The directive keeps the service up to date with the mouse's position and since my service (Factory really) is a singleton, I can pull this data in to other directives/controllers to see what's going on with the mouse.
Here's the problem: I'm trying to allow a specific SVG element to be dragged around, so I created a super simple controller with two functions: a "trackDrag" function that begins tracking and moving a specific element, and a "releaseDrag" which stops tracking/moving the element (drops it where it is, basically).
Inside of my trackDrag function, I attempt to use $scope.$watch to watch the mouse service's current x and y coordinates. Since it's a factory, these values are returned in a function and my watch looks something like this:
$scope.$watch("mouseTrackingService.get()", function(){
// do some stuff here
});
This watch DOES fire off when I first start dragging an element but it doesn't fire as I continue dragging it across the screen. In my "releaseDrag" function, I deallocate the watcher and that seems to work correctly. I'm kind of stumped about why I don't see the watch fire off continuously, even though I can console write out inside of the service and I see that IT is updating correctly.
I've included a plnkr with some sample code below:
http://plnkr.co/edit/g3WEgiQWvd9oXCpFEByn?p=preview
If I just give in and use a $interval then this code works (updating the position every 10ms for example), but really I see that as a much less "angular" way of doing things vs binding.
Ugh, I'm just being dumb. I forgot that $scope.$watch can ONLY WATCH SCOPE VARIABLES.
I fixed this issue by adding the following wrapper around the service:
$scope.currentMouse = function(){
return mouseTrackingService.get();
};
I can then watch currentMouse:
$scope.$watch(currentMouse(), function(){
updateMousePosition(target);
console.log("noticed a change");
});
Of course that gives me that awful Digest error after more than like a half second of dragging:
Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
But that's a different issue entirely.
Sorry folks, nothing to see here. Move along now :-p

Binding not updating, ocModal

I'm using the ocModal directive + service (https://github.com/ocombe/ocModal). When someone wants to delete a record they click the delete button which pops up, via ocModal, a modal to confirm.
oc-modal-close ng-click="deleteNote(id)"
So far so good. Within the controller deleteNote runs. Console.log shows the argument is the id I passed. I call an API to delete the note from my database and on a success call a function to delete the note from the Angular variable $scope.notes.
$scope.removeNote = function(id){
console.log(id);
console.log($scope.notes);
delete $scope.notes[id];
console.log($scope.notes);
};
The id is correct and the $scope.notes after the delete command shows it was correctly deleted. Yet, there is no corresponding update on my front-end. Following advise I've seen on Stackover I used apply() which led to a 'digest cycle already in progress' error. I then tried to use $timeout but while that got rid of the digest cycle error, it didn't solve the problem of the bind.
Is it relevant that the $scope.notes is used by a ng-repeat that then uses a directive? I've experimented a bit and don't think so, but just in case wanted to mention it.
Where are you calling $scope.removeNote from? Is it from your main controller or in the modal's controller? maybe a directive?
It feels like it is hitting the wrong scope. You can try to move notes to $scope.model.notes and see if it helps, as it will ensure the correct scope is referenced, but it's hard to say unless you try and provide a fiddle with the issue occurring.
I've created this simple fiddle trying to mimic an API call with $timeout and it works -> http://jsfiddle.net/7eqsc/1/
angular.module('myApp',[])
.controller('myCtrl',function($scope,$timeout){
$scope.notes={
a:'AngularJS',
b:'Rocks'
}
$scope.addRandom=function(){
$scope.notes[parseInt(Math.random()*10000).toString(36)]='New Item';
}
$scope.removeNote=function(id){
//emulate API call
$timeout(function(){
delete $scope.notes[id];
},2000);
}
});
In general, I recommend placing things under an object, such as ".model.yourArray", it ensures references are kept correctly and will save you a lot of trouble.

angular event doesn't update page

I'm converting a page in a mvc application with a lot of inline jquery javascript to angular using typescript.
The first calls works fine but I have a problem: based on a selection in a dropdown, the event updates several controls in the view, make a few ajax calls and finally update the view with the data from the calls.
Seems the conversion is working fine, but at the end of the call the page isn't updated.
I tried to remove all the old jquery code to avoid problems.
batarang and java console reports no errors.
the final ajax call is done and the result shown in a debug.
All seems to work fine, but the page isn't updated.
How can I find the problem?
thanks
Without seeing any code, it's difficult to answer but if you bind an event to an element and want to update something in the callback, you will have to use $apply
scope.$apply(function () {
// your code
});
$apply will trigger a $digest cycle, and should be used when you want to update something while being outside angular's context.
Most likely you are not handling your asynchronous calls correctly. It's impossible to tell from your question but it is a very common mistake with symptoms as you describe.
Make sure you are updating your model within the .then() method of a promise returned from an $http request. For example
someFnDoingHttpRequest().then(function(data){
// updated the model with the data
})
Also (another common mistake) make sure someFnDoingHttpRequest() returns a promise.
If you want to "find the problem" then you can use the following option.
Go to Internet Explorer (10 or 11).
Select "Internet Options" from the settings menu.
Go to the "Advanced" tab (the last tab)
Settings are listed and select "Display a notification about every script error"
Deselect the "Disable Script debugging (Internet Explorer)" and "Disable script debugging (Other)"
Run the program again, you will get notification about the real issue that happens while displaying actual result.

$compile is only properly inserting my template the first time that I run it

http://embed.plnkr.co/SPGNLd0bcmo2Xt2TAZcB/preview
Here we have a list of personnel information cards. If you click on one, the directive triggers a template to be loaded between that row and the row before it. My problem is that it only works once!
I believe that my problem lies somewhere in my compile statement, but I'm not sure:
$compile(controller.former)(scope);
What honestly baffles me is that even if you just click on the same card over and over again, ignoring all the others, it still just loads the one time. After the first successful load, the Template insertion is coming up empty. that is, isntead of the full template being inserted, i'm just getting:
<!-- ngInclude: 'focus.html' -->
And not the actual template located in that file. Does anybody have any Idea what is going on here?
I apologize in advance for the relatively complex directive, if anybody has any suggestions for refactoring it, I am an open book.
I think the problem I described is in async manner of getting the template from the server side. In case of cached content it didn't work. You used element bind to click which is anti-angular way and non-angular context (thus you needed yo call $apply). And in this case I guess the problem that $scope.apply works before the content of the compiled node itself is processed (because you get the template immediately from the cache). If you call $scope.apply from the $timeout function or (better) change the function to scope function linked with templates via ng-click it works as expected.
I think for the goal are are going to achieve you are taken a bit wrong design which looks a bit weird. Anyway I can show you the problem - you are using separate template which is compiled inside of the ng-include. ng-include directive actually performs $http request with using template cache (default behavior) because of this the data is loaded only once and the template isn't inserted. If you add random seed to the template URL or make $templateCache.remove('focus.html') before compiling that can solve your issue (but I don't think it's good solution as it makes http call each time). You should definitely re-factor your code to make it more clean.
I changed
controller.former = angular.element('<div ng-include="\'focus.html\'"></div>');
to:
controller.former = angular.element('<div><div ng-include="\'focus.html\'"></div></div>');
By wrapping the template into a parent container, I ensure that controller.former is always a reference to a container that holds all of the elements generated during compile time. without this fix, sometimes controller.former would only be selecting the comment node that precedes the inserted material. This resolves causes problems when i'm passing that selector into $animate.enter or $animate.leave, because it ends up only trying to animate that single comment node and ignoring it's next sibling.

Resources