Is there a way to improve dynmically created html compile? - angularjs

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.

Related

ChartistJS : Converting jQuery solution to AngularJS

I am using Chartist JS for my charts in my Angular JS app. The issue is I am seeing this here. There is a JS bin that highlights the issue. The author gives a solution for it. The solution is doing DOM manipulations in Jquery which is easy to do. However with AngularJS the way you manipulate the DOM is via Directives. I have created a plunker here which highlights the same issue in Angular JS but I am confused as to how to put the solution provided by author into my Angular code.
Here is the solution
$('[data-tab]').on('toggled', function (event, tab) {
tab.find('.ct-chart').each(function(i, e) {
e.__chartist__.update();
});
});
Edit: As requested the JSFiddle is updated, so what I am trying to do is. I have three different tabs and three different graphs, whenever I click on them I should see the respective graph. To make the tab behavior possible I have written a basic code using scope and model. which facilitates the changing of tabs. The issue is that the chart is getting created for first or default tab but not for the second and third tab. There is a solution given by the author but I don't know how to implement that in AngualrJS
the jQuery solution that you post is basically finding all the chart references and then doing DOM manipulation and call the update() function.
The key is how to find the chart to update in Angular.
In this case, you can assign a variable when you create a chart. For example:
var chart4 = new Chartist.Bar('#chart4', data1);
var chart5 = new Chartist.Bar('#chart5', data2);
Now you have the reference of the chart. All you have to do is to call update() function to render the chart again.
if (value === "allDrivers") {
$scope.tab = "All";
chart4.update();
}
Here is the working plunker
One thing I like to point out is: right now you need to double click the tab in order to see the chart is being rendered or you resize the browser window. I am still trying to find a way to fix this. But at least this approach gives you an idea how to convert the jQuery solution to Angular solution.
I was able to solve this using angular.element() method. So if you wish you use jquery in your angular code. You have to do this via angular.element method. But make sure to include jquery before angular in your index.html
If jQuery is available, angular.element is an alias for the jQuery
function. If jQuery is not available, angular.element delegates to
Angular's built-in subset of jQuery, called "jQuery lite" or jqLite.
I did not know this. From here it was learning for me. Following advice of #pieterjandesmedt from this post. I was able to do this. For other people who want to learn how this works. I have created a GitHub repo which gives a solution to this issue. The link for problem is given in the question. Hope that helps

Material Design Lite rendering problems with Angular JS

I have some problems using Material Design Lite (getmdl.io). I followed the steps showed in the getmdl.io web in order to install it (actually I use bower), but I always have the same problem, when I change the ng-route in my web, some resources don't render properly, I need to reload the page to get it properly rendered, for example.
First I have this:
then when I reload, I get what I want:
What I cant understand is why other resources like google icons or buttons work correctly but the menu button on the nav bar and other resources like this one need to reaload the page in order to render properly.
I try to include the library using the hosted method and bower method.
Any idea what is going on?
i past in my code this function
angular.module('app', ['ngRoute']).
run(function($rootScope, xxxx, xxx){
$rootScope.$on('$viewContentLoaded', function(event, next) {
componentHandler.upgradeAllRegistered();
});
});
It worked perfect! Good luck..
Libraries like MDL work by waiting for the page to load using the DOMContentLoaded event, scanning the page for things like input elements and manipulating them with JavaScript so that they can inject the bits and pieces needed to work with their components. This works fine on static websites, but the DOMContentLoaded event only fires once, so when Angular performs a page transition, the DOM changes without MDL knowing about it.
Material Design Lite has a section in its FAQ about using MDL on dynamic websites:
Material Design Lite will automatically register and render all elements marked with MDL classes upon page load. However in the case where you are creating DOM elements dynamically you need to register new elements using the upgradeElement function. Here is how you can dynamically create the same raised button with ripples shown in the section above:
<div id="container"/>
<script>
var button = document.createElement('button');
var textNode = document.createTextNode('Click Me!');
button.appendChild(textNode);
button.className = 'mdl-button mdl-js-button mdl-js-ripple-effect';
componentHandler.upgradeElement(button);
document.getElementById('container').appendChild(button);
</script>
Of course, this probably isn't terribly easy to do in your case, since you'd have to manually find each new element and call upgradeElement on it.
Usually, instead of doing this sort of event-based DOM manipulation, Angular uses directives to initiate DOM changes. Consider using a library built to interoperate with Angular, instead, such as Angular Material.

Angular: how to force a recompile of a block

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

backbone render this.$el.html(this.template(data)) slow. what to do?

I have a collection to show.
1. for each model in collection, create a View
2. append view.render().el
I find view.render() takes long, more specifically
this.$el.html(this.template(data)) part.
I need to speed things up and found 'DOM manipulation' is slow.
So I looked for the ways to batch process the rendering but didn't find much.
Question 1.
I wonder if there is a way to batch process and attach the final html to the DOM without attaching each row to the DOM?
(I suspect this.$el.html() does the DOM manipulation. If so, can I somehow not perform the this.$el.html() call in view.render() and later assign the view's el to decrease the DOM interaction?)
Question 2.
Are there other pitfalls or performance blocker when redering views in clients?
edit
addAll: function() {
this.$('#thread-loop').html('');
var fragment = document.createDocumentFragment();
for (var i = 0; i < this.threads.length; i++) {
console.log('adding one');
var thread = this.threads.at(i);
var View = this.threadTypeToViewMap[thread.get('thread_type')];
var view = new View( {model: thread, forumSelector: this.forumSelector} );
fragment.appendChild(view.render().el);
}
this.$el.find('#thread-loop')[0].appendChild(fragment);
// this.threads.each(this.addOne, this);
},
Actually, I realised I'm using the fragment technique.
I nailed down the problem more and it looks like when javascript object has lengthy property (user created data), handlebar takes long to find the property (or so I suspect) in template() call.
Take a look at http://marionettejs.com
They extend Backbone to provide views that are optimized for collections (so you can create the DOM nodes in a loop and only add them to the document after you are done). This is going to really help you with performance.
As for question 2, the things that will give you pitfalls are:
Extensive DOM manipulation
Compiling templates on the client instead of the server (i.e. making individual requests for each template after loading your page)
General JavaScript performance bottlenecks (e.g. using polyfill foreach loops using jQuery or underscore instead of native JS, etc.)
Backbone template renders are slow because by default, they use JavaScript's with structure to scope the variable names properly as you expect. A little-known feature of Backbone templates allows you to skip this rather large performance penalty by assigning a variable to put all your data in (typically just "data"):
var t = _.template('<%= data.x %>', null, {variable: 'data'});
template({x: 42});
instead of the normal:
_.template('<%= x %>');
template({x: 42});
Do this, and you will see huge performance gains. This will probably solve your performance issues immediately.
Benchmark: http://jsperf.com/underscore-template-function-with-variable-setting
For Q1 ->
If your $el is not in the dom already, you can append all the $el's into a Document Fragment and then inject the Document Fragment into the page at once. JQuery's John Resig has written a nice writeup about how document fragments work : http://ejohn.org/blog/dom-documentfragments
A couple of things you could try on top of using the doc fragment:
1- If you want to use the for loop, cache the length prior to looping. var threadLength = this.threads.length;
2- That said, I would opt for using _.each instead of the for loop.
3- if you do 2, the threads model should be easily accessed. I don't know if there is any hit to using get(), but you could just access the model.attributes.thread_type directly.
3- Is your template cached?
4- Why are you changing the html of #thread-loop twice? Is that needed? If you need to access it twice, then cache that selector also.

$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