Angular + Ionic loading all content via XHR - angularjs

We have an Angular + Ionic app that we are planning on running through Cordova, but having an issue with performance that we are trying to track down.
What we are seeing in Chrome Dev tools Network tab when running either locally or on the built app, is the following:
Duplicate loading of CSS
XHR requests to get every single template file our Angular UI router links to, without having visited the routes yet
As an example:
And line 3167 (indicated with a star) from the angular.js source:
append: function(element, node) {
var nodeType = element.nodeType;
if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
node = new JQLite(node);
for (var i = 0, ii = node.length; i < ii; i++) {
var child = node[i];
element.appendChild(child); *
}
},
I've never seen anything like it - we've checked all the basics (duplicate script/css includes, etc), disabled Ionic caching, etc.
I'm stripping things down to the studs to see what could be causing this, but hoping someone else has seen this and can offer some advice on where to start looking.
UPDATE
The duplicate CSS appears to be due to our index.html file which bootstraps our Angular App was incorrectly pointed to as a state in the UI Router config.
So the root issue is the spurious/unexpected XHR pulls to all of the static files in the app (angular ui templates, directive templates).

The way I deal with html templates is to cache them all at compile time using gulp-ng-templates or grunt-angular-templates (depending which flavor of task manager you like nowadays).
Since we are dealing with apps, the content should better be eager-loaded rather than lazy-loaded (as long as you count their total size in MB), thus saving some bandwidth and improving the overall user experience. In addition it might just fix your issue.
The beauty of caching the templates at compile time is that your implementation doesn't need to know where they are coming from (server or the caching layer), and thus you don't need to change any code.
P.S. I understand that my answer will not fix your actual problem, but it might just solve 2 issues at the same time.

Well, when a state is activated, the templates are automatically inserted into the ui-view of its parent state’s template.
You should check how you have defined your states. And/or share your state definitions with us :)

Related

How can I render an Angular directive in a popup window, allow it to communicate with the main window?

Users of my app want to be able to detach certain application components from the SPA and move them onto their second monitor while retaining their functionality.
What I don't want is to load the entire SPA in a popup window just to show this one view. I've found that I can append a template to a popup window's body and $compile it there using the scope from the main window. This mostly works, but any directive that uses the 'require' syntax will ultimately fail because where ever Angular is looking for the required directive it isn't finding it.
Is there a better way of doing what I'm trying to achieve?
Or any ideas what I can try to solve the "Controller X required by directive Y can't be found" issue?
function createWindowForPoppedOutPane(pane) {
var features = 'menubar=no';
if (pane.top) features += ',top=' + pane.top;
if (pane.left) features += ',left=' + pane.left;
if (pane.width) features += ',width=' + pane.width;
if (pane.top) features += ',height=' + pane.height;
pane.window = $window.open('', '_blank', features);
copyStyleSheetsToWindow(pane.window);
var paneScope = scope.$new(false);
paneScope.pane = pane;
var paneTemplate = $($templateCache.get('pop-out-pane-template'));
paneTemplate.append($templateCache.get(pane.template));
scope.$evalAsync(function () {
pane.window.document.title = pane.title;
angular.element(pane.window.document.body).append(paneTemplate);
$compile(paneTemplate)(paneScope);
startPoppedOutPaneWatcher();
});
}
The correct way to handle this situatin it to load the app in the new window. This would be no different than someone doing a right-click open in new window. If you are sending appropriate cache headers for your JS and CSS files the use will not need to download the files.
Attempting to build a second sub-sub site where some components can be used is going to be a maintenance nightmare.
An alternative would be to create a second top level module that pulling in only the components you need but again this is a lot of ectra work on your end and your users would need to download additional files.

Mozilla Web Extensions: Insert UI component into page

I am writing a Web Extension for Firefox that needs to insert a substantial amount of additional functionality inside pages retrieved using certain URLs.
I was able to quickly create a content script that is called whenever a certain page is opened thanks to the tutorial at Mozilla's web site, but now I'm stuck on actually inserting html fragment into the page.
I've been at it for hours but to no avail. Here's what I've considered and tried:
iframe didn't work as apparently some security policy doesn't allow using iframes pointing to local resources and the last comment here even tells that I'm supposed to use panel instead of iframe
Using Panel doesn't work for me for two reasons:
I couldn't find a way to open a Panel using my own custom code (the sample by the link above fails with ReferenceError: require is not defined)
I'm guessing that I can open a panel in a Web Extension only by using a bowserAction but that would put the button on the toolbar while I need it in the page itself
According to documentation I can have only one Panel instance open for the whole browser and it would automatically close upon interacting with any other browser element
Lastly I thought about just loading html from a resource file packed into the extension and feeding it into the page using innerHTML but I couldn't find any API to load text from a resource
Just using DOM API doesn't work for me since it would take forever to code creation of all the elements
I can't believe I didn't notice it for so long, but I finally got it all working as I need. While doing that I even came up with an alternative approach, so here goes.
But first here's the main reason why I dismissed all other possible approaches besides using iframe:
I needed the UI elements added by extension to use their own UI styles and wanted to take advantage of modern frameworks (e.g. jQuery and Bootstrap) and I didn't want to run into problems of conflicting CSS and JavsScript later.
And I actually noticed early on that CSS in the page that I'm embedding into do override Bootstrap styles.
Preferably I also didn't want to affect century old markup of the page that I'm embedding into.
Option A - IFRAME with external source file
In the end it turned out that the only thing I was missing is the web_accessible_resources setting in the manifest.json. Once I added the html file used as source for the iframe into that list, it all just started working.
// manifest.json:
{
"manifest_version": 2,
...
"web_accessible_resources": [
"data/my.html"
],
"content_scripts": [
{
"matches": ["*://*"],
"js": ["js/my.js"]
}
]
}
// js/my.js
var addonFrame = document.createElement ("IFRAME");
addonFrame.style = "position: absolute; bottom: 0; right: 0; width: 150px; height: 38px;";
addonFrame.frameBorder = "0";
addonFrame.src = chrome.extension.getURL ("data/my.html");
document.body.appendChild (addonFrame);
Option B - IFRAME with inline HTML in JS
Before I finally got the first approach working, my experimentation led me to another working approach - inserting HTML into the iframe directly in the content script.
// js/my.js
var addonFrame = document.createElement ("IFRAME");
addonFrame.style = "position: absolute; bottom: 0; right: 0; width: 150px; height: 38px;";
addonFrame.frameBorder = "0";
var addonHtml = "<!DOCTYPE html>\n\
<html>\n\
<head>\n\
<meta charset='UTF-8'>\n\
<title>Title of the document</title>\n\
</head>\n\
<body>\n\
...\n\
</body>\n\
</html>";
addonFrame.src = 'data:text/html;charset=utf-8,' + encodeURI (addonHtml);
document.body.appendChild (addonFrame);
Even though I ended up using option A in the end, let me outline some pros and cons:
Option A is obviously more canon: view (html) is clearly separated from behavior (js) and all files have content appropriate for their type (except for small exception of building iframe element in JS).
So it should be easier to support going forward.
Option A doesn't allow to use inline scripts in the frame (https://developer.chrome.com/extensions/contentSecurityPolicy). This makes prototyping harder but ultimately should be a plus.
For some reason that is still unclear to me, I cannot use # in the inserted html in option B.
Option B makes doing ajax calls from the add-on frame to the original server easier since the frame source is considered to be from the same domain as the original web page.
In option A, I had to use Window.postMessage in the frame in order to ask my content script inserted into the original page to make an ajax request and give me back the response (the second part was especially hard since there's nothing like jQuery or Prototype available there).

AngularJS register controller once

That's what I'm doing. There is application with pages and different controls that may be put on pages by site admin/editor. All pages share one ng-app defined on master page. All controls are supplied with .js files with angular controllers. Let's suppose that I have an image gallery block:
<div ng-controller='imageGalleryCtrl'>
do something amazing here
</div>
<script src='imageGallery.js'></script>
Inside script there is a simple controller registration like:
angular.module('myApp').controller('imageGalleryCtrl', ... );
So. If I have 10 image galleries, I'll execute controller registration 10 times. It looks like this will work, but hell - I don't want it to be so =)
For now I just have all controls' scripts registration on a master page, but I don't like it as well, because if there is no image gallery on a page, I don't want it's script be downloaded during page load.
The question is - is there any proper way to understand if controller have been registered in a module already and thus prevent it from re-registering?
---------------
Well, though I've found no perfect solution, I must admit that the whole idea isn't very good and I won't think about it before my site will grow too big to assemble whole angular app on master page.
You should declare your controller but once. Instead of having one controller per gallery, have your single controller handle all image galleries. The controller should make a request to the REST backend to fetch the images of the desired gallery.
I see that instead of ng-view, you're using the ng-controller directive, indicating that probably you're not using Angular's routing. Try switching to using routes.
Have a look at Angular.js routing tutorial. It shows you how to use the ngRoute module. Then, in the next chapter, the use of $routeParams is described. Via the $routeParams service, you can easily say which gallery should be displayed by providing its ID in the URL; only one controller will be necessary for all your galleries.
If you really must check whether a given controller has been declared, you can iterate through the already declared controllers (and services... and pretty much everything else) by checking the array angular.module("myApp")._invokeQueue. The code would probably look something like this (not tested!):
var isRegistered = function(controllerName)
{
var i, j, queue = angular.module("myApp")._invokeQueue;
for (i = 0, j = queue.length; i < j; ++i) {
if (
queue[i][0] === "$controllerProvider"
&& queue[i][1] === "register"
&& queue[i][2][0] === controllerName
) {
return true;
}
}
return false;
};
Bear in mind however that while this may (or may not) work, it's far from being the correct thing to do. It's touching Angular's internal data that's not meant to be used in your code.

prevent Mustache from loading template from cache

I'm using Backbone.js with mustache.js, and I'm loading my templates using ajax. my problem is that the templates are being loaded from cache(refreshing using ctrl+F5 if that matters!). Now I have made changes to the template but it's still loading the old version of it. It's working perfectly fine in incognito. Is there a way to prevent this? Maybe prevent Mustache from caching the template?
The code that renders the template is:
$.get(this.templatesPath + this.template, function(resTemplate){
var html = Mustache.render(resTemplate, that.personData);
that.$el.html(html);
});
My first thought was to use some other function instead of "Mustache.render()" like maybe "Mustache.to_html()". But looking at the
Source Code
reveals that to_html() merely calls render().
Any thoughts?
Apologies for digging up this very old question, but I was searching for the answer to a similar question and didn't end up finding it anywhere. This question is one of the first that shows up when searching "mustache disable caching".
I am using Mustache and Express with the mustache-express module. I was able to disable caching with the following:
const Mustache = require('mustache-express')();
delete Mustache.cache;
I hope this helps someone else in the future.

Backbone Marionette using Require.js, Regions and how to set up

I'm currently writing a Backbone Marionette app which ultimately amounts to about 6 different "screens" or pages which will often times share content and I am unsure of how to best structure and access Regions.
I am using the app/module setup described here: StackOverflow question 11070408: How to define/use several routings using backbone and require.js. This will be an application which will have new functionality and content added to it over time and need to be scalable (and obviously as re-usable as possible)
The Single Page App I'm building has 4 primary sections on every screen: Header, Primary Content, Secondary Content, Footer.
The footer will be consistent across all pages, the header will be the same on 3 of the pages, and slightly modified (using about 80% of the same elements/content) on the remaining 3 pages. The "morecontent" region will be re-usable across various pages.
In my app.js file I'm defining my regions like so:
define(['views/LandingScreen', 'views/Header', 'router'], function(LandingScreen, Header, Router) {
"use strict";
var App = new Backbone.Marionette.Application();
App.addRegions({
header: '#mainHeader',
maincontent: '#mainContent',
morecontent: '#moreContent',
footer: '#mainFooter'
});
App.addInitializer(function (options) {
});
App.on("initialize:after", function () {
if (!Backbone.History.started) Backbone.history.start();
});
return App;
});
Now, referring back to the app setup in the aforementioned post, what would be the best way to handle the Regions. Would I independently re-declare each region in each sub-app? That seems to be the best way to keep modules as independent as possible. If I go that route, what would be the best way to open/close or hide/show those regions between the sub-apps?
Or, do I keep the Regions declared in app.js? If so, how would I then best alter and orchestrate events those regions from sub-apps? Having the Regions defined in the app.js file seems to be counter-intuitive to keeping what modules and the core app know about each other to a minimum. Plus, every example I see has the appRegions method in the main app file. What then is the best practice for accessing and changing those regions from the sub-app?
Thanks in advance!
I actually have a root app that takes care of starting up sub-applications, and it passes in the region in which they should display. I also use a custom component based off of Backbone.SubRoute that enables relative routing for sub-applications.
check out this gist: https://gist.github.com/4185418
You could easily adapt it to send a "config" object for addRegions that defines multiple regions, instead of the region value I'm sending to the sub-applications' start method
Keep in mind that whenever you call someRegion.show(view) in Marionette, it's going to first close whatever view is currently being shown in it. If you have two different regions, each defined in its own app, but both of which bind to the same DOM element, the only thing that matters is which region had show called most recently. That's messy, though, because you're not getting the advantages of closing the previous view - unbinding Event Binders, for example.
That's why, if I have a sub-app that "inherits" a region from some kind of root app, I usually just pass in the actual region instance from that root app to the sub-app, and save a reference to that region as a property of the sub-app. That way I can still call subApp.regionName.show(view) and it works perfectly - the only thing that might screw up is your event chain if you're trying to bubble events up from your region to your application (as the region will belong to the root app, rather than the sub-app). I get around this issue by almost always using a separate instance of Marionette.EventAggregator to manage events, rather than relying on the built-in capabilities of regions/views/controllers/etc.
That said, you can get the best of both worlds - you can pass the region instance into your sub-app, save a reference to it just so you can call "close", then use its regionInstance.el property to define your own region instance pointing to the same element.
for(var reg in regions) if regions.hasOwnProperty(reg) {
var regionManager = Marionette.Region.buildRegion(regions[reg].el,
Marionette.Region);
thisApp[reg] = regionManager;
}
It all depends on what your priorities are.
I personally prefer to use the modules in my Marionette application. I feel it removes the complexity that require.js adds to your application. In an app that I am currently working on, I've created one app.js file that defines my backbone application but I am using a controller module that loads my routes, fills my collections and populates my regions.
app.js ->
var app = new Backbone.Marionette.Application();
app.addRegions({
region1: "#region1",
region2: "#region2",
region3: "#region3",
region4: "#region4"
});
app.mainapp.js ->
app.module('MainApp', function(MainApp, App, Backbone, Marionette, $, _) {
// AppObjects is an object that holds a collection for each region,
// this makes it accessible to other parts of the application
// by calling app.MainApp.AppObjects.CollectionName....
MainApp.AppObjects = new App.AppObjects.Core();
MainApp.Controller = new Backbone.Marionette.Controller.extend({
start: function() {
// place some code here you want to run when the controller starts
} //, you can place other methods inside your controller
});
// This code is ran by Marionette when the modules are loaded
MainApp.addInitializer(function() {
var controller = new MainApp.Controller();
controller.start();
});
});
You would then place your routes inside another module that will be accessed in the controller.
Then in the web page, you would start everything by calling.
$(function () {
app.start();
});
Marionette will automatically run and load all of your modules.
I hope this gets you started in some direction. Sorry I couldn't copy and past the entire application code to give you better examples. Once this project has been completed, I am going to recreate a demo app that I can push to the web.

Resources