initial data loading in angularjs - javascript

When building a web app where every page depends on many data sources, what's the best way to fetch the initial bits of data? When I look at twitter, I see the tweets that are visible on page load are in the HTML source, and more tweets are loaded in using AJAX as you scroll down. But there's no convenient way to get data that's already in the DOM to be inserted into the model.
Making a request for the initial data, immediately after page load seams stupid, because you've just made a lot of roundtrips to the server to fetch css, html and javascript. Would it be a bad idea to insert the data into a javascript tag on the page, so a javascript function can add the initial data?
I'm specifically asking for angularjs, but if there's an general technique, please let me know as well.

You'll be referencing your controller anyway on page load, so you won't have to have an inline script tag.
You can either set a default model and use the attribute ng-bind on initial load, or call a function to pass back data.
It's pretty typical to fetch data on load in angularjs.

Would it be best to couple Angularjs with an HTTP Client in the backend like Zend_Http_Client or Guzzle to let the server fetch the data. Then, pass the data as json to javascript upon render.
I know Angularjs is designed for Single Page applications. That's why it makes sense that it lazy loads the data.
However, if we're going to move to the approach where we still render the page dynamically and still delegate the task of organizing the content to Angularjs. What framework will be suitable to contain the AngularJS views. Right now, project templates like angular-seed are all static..
That is, the idea is the server serves a page with the embedded json object. Then angular, takes over in the client side, Fetching additional content where needed.
So instead of just a single page of contact (e.g: index.html), we would have several pages like profiles.html, products.html. The help of the backend would be particularly helpful say you have a section which doesn't change often like your username on the top right side of the page. For me, I just think it's better to have these data preloaded in your page and not have to ask the server after the page has been loaded.
As bigblind have noticed, this seems to be the way sites like facebook, gmail, twitter does it. They contain the data embedded on page load. Then, load additional content via services afterwards.
The idea is something like below:
Webservice <---------- Backend------------> Frontend
<------------------------------------------
Backend delegates the task of querying the webservice to provide initial data in the rendered page to the client. Then client, can directly connect to webservice to fetch additional content.
Using the above setup.. What is the ideal development stack?

One way to do it is to create a directive that handles the initialization before binding happens.
For example:
app.directive('initdata', function() {
return {
restrict: 'A',
link: function($scope, element, attrs) {
if ( attrs.ngBind !== undefined)
{
$scope[attrs.ngBind] = attrs.initdata ? attrs.initdata : element.text();
}
}
};
});
This directive takes either the attribute value as initial value for the bound $scope property, or the textvalue of the element.
Example usage:
<div initdata="Foo Bar" ng-bind="test1"></div>
<div initdata ng-bind="test2">Lorem Ipsem</div>
Working example on http://jsfiddle.net/6PNG8/
There's numerous way to elaborate on this; for example parsing the initdata as json and merging it with the scope, and making it work for more complicated binds, like $root.someprop. But the basis is remarkably simple.

According to the answers on this question, a JSON object in a script tag on the page seems to be the way to go. If ayone comes up with a better idea, I'll accept your answer.

Related

Caching URL view/state with parameters

I'm making a mobile app using Cordova and AngularJS. Currently I have installed ui-router for routing but I'm open to any other alternative for routing.
My desire: I want to cache certain views bound with parameters. In other words I want to cache paths (or pages).
Example situation: let's say that we see some dashboard page, click on some book cover which redirects to the path book/2. This path is being loaded for the first time into app. Router redirects from HomeController to BooksController (whatever the name). Now the BooksController loads data for given $stateParams (book id = 2) and creates view filled with info about chosen book.
What I want in this situation:
I go back to the dashboard page - it is already loaded (cached?)
I choose book #2 again
Controller or router notices that data about this book is already loaded
The view isn't being recreated, instead it's being fetched from cache
Actually, it would be best to cache everything what I visit based on path. Preloading would be cool too.
Reason: performance. When I open some list of books then I want it to show fast. When view is being created every time, then animation of page change looks awful (it's not smooth).
Any help would be appreciated.
EDIT:
First of all, since I believe it's a common problem for many mobile HTML app programmers, I'd like to precise some information:
I'm not looking for hacks but a clear solution if possible.
Data in the views uses AngularJS, so YES, there are things like ng-bind, ng-repeat and so on.
Caching is needed for both data and DOM elements. As far as I know, browser's layout operation is not as expensive as recreating whole DOM tree. And repaint is not what we can omit.
Having separate controllers is a natural thing. Since I could leave without it I cannot imagine how it would work anyway.
I've got some semi-solutions but I'm gonna be strict about my desire.
Solution 1.
Put all views into one file (I may do it using gulp builder) and use ng-show. That's the simplest solution and I don't believe that anyone knowing AngularJS would not think about it.
A nice trick (from #DmitriZaitsev) is to create a helper function to show/hide element based on current location path.
Advantages:
It's easy.
KIND OF preload feature.
Disadvantages:
all views have to be in a single file. Don't ask why it's not convenient.
Since it's all about mobile devices, sometimes I'd like to "clear" memory. The only way I can think of is to remove those children from DOM. Dirty but ok.
I cannot easily cache /book/2 and /book/3 at the same time. I would have to dynamically create DOM children on top of some templates for each view bound with parameters.
Solution 2.
Use Sticky States AND Future States from ui-router-extras which is awesome.
Advantages:
Separated views.
Very clear usage, very simple since it's just a plugin for ui-router.
Can create dynamic substates. So it would be possible to cache book1, book2 but I'm not sure about book/1 and book/2
Disadvantages:
Again, I'm not sure but I didn't found an example with caching a pair/tuple (view, parameters). Other than that it looks cool.
This is precisely the problem I had to solve for my site 33hotels.com. You can check it and play with the tabs "Filter" and "Filter List" (corresponding to different Routes), and see that the View is updated instantly without any delay!
How did I do it? The idea is surprisingly simple - get rid of the Router!
Why? Because the way the Router works is it re-compiles the View upon every single Route change. Yes, Angular does cache the Template but not the compiled View populated with data. Even if data do not change! As the result, when I used the Router in the past, the switch always felt sluggish and non-reactive. Every time I could notice annoying delay, it was a fraction of second but still noticeable.
Now the solution I used? Don't re-compile your Views! Keep them inside your DOM at all times! Then use ng-hide/ng-show to hide/show them depending on the routes:
<div ng-show="routeIs('/dashboard')">
<-- Your template for Dashboard -->
</div>
<div ng-show="routeIs('/book')">
<-- Your template for Book -->
</div>
Then create a function routeIs(string) inside your Controller to test if $location.path() matches string, or begins with string as I am using it. That way I still get my View for all pathes like /book/2. Here is the function I am using:
$scope.routeBegins = function () {
return _.some(arguments, function (string) {
return 0 === $location.path().indexOf(string);
});
};
So no need to be smart with caching - just keep it in the DOM. It will cache your Views for you!
And the best part is - whenever your data is changed, Angular will instantly update all the Views inside your DOM, even the hidden ones!
Why is this awesome? Because, as user, I don't have to wait for all the parsing and compiling at the moment I want to see the result. I want to click the tab and see my results immediately! Why should the site wait for me to click it and then begin all the re-compiling as I am waiting? Especially when this could be easily done before, during the time my computer is idle.
Is there any downside? The only real one I can think of is loading memory with more DOM elements. However, this actual byte size of my views is negligible, comparing e.g. with all JS, CSS and images.
Another possible but avoidable downside is the re-compilation cost of the hidden views. This is where you can get smart and avoid computation-heavy parts depending on the current routes.
Also, you are not re-compiling the whole View, just the parts affected by data changes, which also lowers computational cost.
I find it quite remarkable that everyone is using Routes and seems to be completely unaware (or ignorant) of this problem.
1) About static pages in the app (views), angular takes care of loading them.
for example: for your dashboard page you need not worry about caching the page, as angular will take care of it. Angular will only load the dashboard view once and on all next requests for the dashboard view, angular will just show you the view(not load the file for view), if it is all a static view without any data loaded by ajax calls.
2) if your dashboard is itself loading the book list(or similar data) via ajax, then you can tell your controller to only load the data once and store it to localstorage and on subsequent requests to the dashboard page can only load the data from the localStorage.
3) similar approach can be used when your BooksController loads the data into a view. You can check in your BooksController if the request for a particular book is been previously made and if not your can store it to localstorage or database. and if the data was previously requested then you can load the same data from the storage without making a request to server.
Example situation:
say user makes request for book1, then
your controller i.e BooksController check whether the same data was requested before,
if not, you can load the data via the ajax call from server and also save it to local storage.
if it was loaded before you will load the data stored in the localstorage or in the database.
If you're using ui.router, then you should take a look at ui.router extras, specifically the sticky states module. This allows you to cache the state 'tree' (including rendered views) so they don't have to be compiled or re-rendered on state changes.
http://christopherthielen.github.io/ui-router-extras/
Here's a demo:
http://christopherthielen.github.io/ui-router-extras/example/sticky/#/

AngularJS: what is $templateCache?

While profiling a large AngularJS app, I started tracking $templateCache.
https://docs.angularjs.org/api/ng/service/$templateCache
What is this object? Does it store the partials as they are stored on disk or the final rendered HTML from the route?
I've added some code like this to my controller to make sure it's not persisting anything:
$scope.$on('$destroy', function cleanup() {
$templateCache.removeAll();
});
EDIT: the reason I am looking into this is that a single (not every) controller is loading a large partial with several loops inside of it, depending on User input it could be about 100 input fields with large <select> statements and many custom directives. The client is requesting it be this way and I want to make sure that all of this is getting disposed during routing.
To clarify the question: $templateCache is only storing the partials as they appear on disk, no routing or rendering information is being stored?
By default, while loading templates, Angular looks inside $templateCache and fetches templates over XHR when it cannot find them locally in its $templateCache.
When the XHR request is slow or our template is large enough, it can seriously negatively impact end users’ experience .
instead of fetching templates XHR, we can fake the template cache loading by wrapping it into a JavaScript file and shipp the JavaScript file along with the rest of the application.
Sample :
angular.module('myApp')
.run(['$templateCache', function($templateCache) {
$templateCache.put('xyz.html', ...);
}]);
So, unless you are very much sure to remove the cached templates form $templateCache, don't do that.
If I remember correctly, it's a object where partials are saved. When making a request to any html file (through routing, directives or include) , angular first looks into $templateCache, and if required file doesn't exist, it will make a request for it and will put it in the cache. But if it does exists, it is taken from there. So if you are using your directive 100 times per page, it will only make one request for your directive file, or if you navigate back and forth your app, it won't request files that were already loaded.
Does it make sense to add $templateCache.removeAll(); on each controller? Only if it's really small application, otherwise I suggest not messing around with it..

angularJS & web2py: calling python from ng/javascript/html

I'm using AngularJS (mainly the client side) and web2py (mainly the server side)together in an app.
I have an issue now.
At a point of the program, I use AngularJS to get some data from the client and these data are store in an AngularJS variable. I planed to use $http.post to submit these data to database directly, but it didn't work due to cross-orign problem.
Right now I'm trying to pass these data(they are in JSON format) back to web2py and let web2py insert these data to database.(similar to submitting a SQLFORM).
Is there anyway I could pass these data as an argument to an web2py function and invoke that function within javascript code?
Possible approach in my mind:
1) Since I could write python in html using {{}}, and I could write html in javascript, could I write python code within javascript using something like: document.write({{python code}}) ?
I tried this but whatever html I write it goes to a brand new html page. I also tried document.getElementById('testDiv').write("<p></p>"); But it doesn't work.
2)use ajax, I'm not familiar with ajax, any example will be really appreciated!
Any thoughts?
Thank you all!
ok so you got me lost for a second there, lets see if i got it right
1- angular as your frontend
2- python as your backend
3- you are rendering an html document in python and delivering it to the browser
4- since python template language uses {{}} as delimiter am assuming you changed the angulars delimiters too
either using ajax or reload you'll need to provide a python post handler script. that takes your data and makes the DB update. if this is going to be a pattern and you are going to be making AJAX CRUD operations, you should use angular resources ngResource if not a simple
$http.post(url,data).success(function(response){})
https://docs.angularjs.org/api/ng/service/$http#post
where url would be your form submission handler url.
if you where to use a form you'll need to set the target to an iframe hidden in your page and the response should a script tag that gets the scope pertinent to your controller and let him know the result of the operation. this is an old approach, but handy when it comes to send information to sites that don't allow CORS which by the way might be the solution to your problem, when storing data directly to your db, you might just need to enable CORS headers in your storage engine API and that should allow you to submit information even when coming from a different domain
After hours of struggles and countless google, here's my workaround solution:
Main problem: the data are stored in AngularJS but AngulatJS could not submit data to database through API due to cross-orign issue. But Web2py could submit data to database using sqlform.
My approach:
1.)When the user click the submit button, invoke 'ng-click="submitBtn()"'.
submitBtn() is a function of the ng-controller, which has access to the data.
2.)In submitBtn(), the function first write data into web2py's sqlform through
document.getElementById('inputId').value=$scope.data;
then the function click the sqlform submit button through
document.getElementById('submitBtn').click();
It took me a lot time to figure out those element ids of fields in web2py's auto-generated sqlform. The way to find them is using developers' inspect element tool in a browser and see the source code directly.
Hope this will help someone will face the same issue!

Generating HTML in JavaScript vs loading HTML file

Currently I am creating a website which is completely JS driven. I don't use any HTML pages at all (except index page). Every query returns JSON and then I generate HTML inside JavaScript and insert into the DOM. Are there any disadvantages of doing this instead of creating HTML file with layout structure, then loading this file into the DOM and changing elements with new data from JSON?
EDIT:
All of my pages are loaded with AJAX calls. But I have a structure like this:
<nav></nav>
<div id="content"></div>
<footer></footer>
Basically, I never change nav or footer elements, they are only loaded once, when loading index.html file. Then on every page click I send an AJAX call to the server, it returns data in JSON and I generate HTML code with jQuery and insert like this $('#content').html(content);
Creating separate HTML files, and then for example using $('#someID').html(newContent) to change every element with JSON data, will use even more code and I will need 1 more request to server to load this file, so I thought I could just generate it in browser.
EDIT2:
SEO is not very important, because my website requires logging in so I will create all meta tags in index.html file.
In general, it's a nice way of doing things. I assume that you're updating the page with AJAX each time (although you didn't say that).
There are some things to look out for. If you always have the same URL, then your users can't come back to the same page. And they can't send links to their friends. To deal with this, you can use history.pushState() to update the URL without reloading the page.
Also, if you're sending more than one request per page and you don't have an HTML structure waiting for them, you may get them back in a different order each time. It's not a problem, just something to be aware of.
Returning HTML from the AJAX is a bad idea. It means that when you want to change the layout of the page, you need to edit all of your files. If you're returning JSON, it's much easier to make changes in one place.
One thing that definitly matters :
How long will it take you to develop a new system that will send data as JSON + code the JS required to inject it as HTML into the page ?
How long will it take to just return HTML ? And how long if you can re-use some of your already existing server-side code ?
and check how much is the server side interrection of your pages...
also some advantages of creating pure HTML :
1) It's simple markup, and often just as compact or actually more compact than JSON.
2) It's less error prone cause all you're getting is markup, and no code.
3) It will be faster to program in most cases cause you won't have to write code separately for the client end.
4) The HTML is the content, the JavaScript is the behavior. You're mixing both for absolutely no compelling reason.
in javascript or nay other scripting language .. if you encountered a problem in between the rest of the code will not work
and also it is easier to debug in pure html pages
my opinion ... use scriptiong code wherever necessary .. rest of the code you can do in html ...
it will save the triptime of going to server then fetch the data and then displaying it again.
Keep point No. 4 in your mind while coding.
I think that you can consider 3 methods:
Sending only JSON to the client and rendering according to a template (i.e.
handlerbar.js)
Creating the pages from the server-side, usually faster rendering also you can cache the page.
Or a mixture of this would be to generate partial views from the server and sending them to the client, for example it's like having a handlebar template on the client and applying the data from the JSON, but only having the same template on the server-side and rendering it on the server and sending it to the client in the final format, on the client you can just replace the partial views.
Also some things to think about determined by the use case of the applicaton, is that if you are targeting SEO you should consider ColBeseder advice, of if you are targeting mobile users, probably you would better go with the JSON only response, as this is a more lightweight response.
EDIT:
According to what you said you are creating a single page application, if this is correct, then probably you can go with either the JSON or a partial views like AngularJS has. But if your server-side logic is written to handle only JSON response, then probably you could better use a template engine on the client like handlerbar.js, underscore, or jquery templates, and you can define reusable portions of your HTML and apply to it the data from the JSON.
If you cared about SEO you'd want the HTML there at page load, which is closer to your second strategy than your first.
Update May 2014: Google claims to be getting better at executing Javascript: http://googlewebmastercentral.blogspot.com/2014/05/understanding-web-pages-better.html Still unclear what works and what does not.
Further updates probably belong here: Do Google or other search engines execute JavaScript?

Lazy loading in Knockout JS

I'm trying to load and parse json data from an external source into a table via Knockout JS. So far, everything has been successful through the following code:
// Snippet
var self = this;
self.notices = ko.observableArray([]);
self.currentTab = ko.observable(5);
ko.computed(function() {
$.getJSON('http://json.source.here.com/tab/'+ko.toJS(self.currentTab), function(threads) {
if (threads !== null) {
self.notices(threads);
} else {
self.notices([]);
}
});
}, self.notices);
When a user clicks on a certain tab it would load the json data (forum threads) based on the selected tab value (self.currentTab) onto the table in the form of rows (self.notices).
Everything works as expected however, I noticed while browsing around other pages that do not have the above bindings, the json is still being loaded ($.getJSON is fired). I'm concerned that this may have some detrimental effects on the performance of my website as it is loading the json source even though it is not needed.
EDIT: I figured this out through Google Chrome's developer console.
I currently have my view model in a JavaScript file that is being used by every other pages as well. It consists of bindings for all the pages.
My question is, how can I load the json data on a specific page or only when the bindings are present - lazy loading? Preferably, I would like to keep all the bindings in a single JavaScript file, I do not want to separate them out and load them on a per page basis.
Here is an article that I wrote on a simliar topic a little while back: http://www.knockmeout.net/2011/06/lazy-loading-observable-in-knockoutjs.html
In your case, I think that you really want to add some guards around the $.getJSON call to ensure that it is only making AJAX requests when you are in the appropriate state (on the appropriate tab).
Along with that, the blog post describes using the deferEvaluation flag on a computed observable to ensure that the logic does not run until someone binds against the computed observable (in your case, you have an anonymous computed observable, but you could add it to your view model as a property and bind against it in your view. Without this flag, the evaluation code will run when you create the computed observable, which is not desirable in your case.

Categories