Appery.io Under the Hood Part 1: RequireJS and AngularJS
In the “Appery.io under the Hood” series, we’ll be explaining in detail how Appery.io works. We hope that this information will give you a deeper insight into how to make better apps by giving you a deeper understanding of the platform.
Loading resources when needed
When developing big scalable web apps, the question of how to manage tons of libraries and services arises pretty quickly. There are a few key reason why a powerful and flexible dependency management tool becomes mandatory:
- The vast majority of libraries dictate their own requirements for dependencies. This makes it critically important to load and launch resources in the appropriate order
- Loading all the libraries and other resources at once increases network traffic and should be avoided when possible. This is even more important for mobile apps
- App loading time can be significantly decreased when loading only the required libraries:
- A ready event will be fired only once for all the required resources
- For mobile devices and apps, page size can be optimized as much as possible to prevent browser or OS failures
- Overloading the device CPU by loading unnecessary libraries will decrease battery life
- The bigger the development team, the more complicated library dependency management becomes
This is why Appery.io has chosen RequireJS to solve the cumbersome process of dependency management. Let’s see how RequireJS is used in Appery.io apps.
RequireJS
RequireJS implements an AMD (Asynchronous Module Definition) approach and allows the declarative description of dependencies among app modules. RequireJS also guarantees that all module dependencies will be resolved before module execution. In the AMD pattern means each module has its own isolated variables area. This makes your app more stable by reducing the possibility of namespace conflicts.
AngularJS with RequireJS
AngularJS has its own built-in dependency manager, but without the option to asynchronously load external modules. This is done by RequireJS.
The first problem when trying to combine RequireJS and AngularJS in one app is that AngularJS doesn’t support RequireJS. This can be only partially resolved by selecting “not AMD modules support” in RequireJS.
App launch
When AngularJS is loaded in the page, it searches for a ng-app directive as an entry point and ignores everything outside of this tag. When ng-app
tag is found, AngularJS starts to parse the whole DOM-tree, executing all found directives and expressions.
This approach doesn’t fit Appery.io app development because of a really simple reason: when AngularJS is ready, other app dependencies such as libraries for external services, REST API services, and other resources will not be ready. As a result, there will be a lot of errors caused by references to not yet loaded modules.
Let’s look at this example:
require(["require", "angular", "app", "ui-bootstrap"], function (require, angular) { angular.bootstrap(document.documentElement, ["AppModule_ProjectName"]); });
In this example, an app module called app
is loaded as well as angular
and ui-bootstrap
. AngularJS is also aware of the entry point and module name which will maintain this element. Up to this point, the app module is already loaded through the app
dependency.
The app is launched.
Flicker effect during app launch
The structure above has a small but very undesirable problem. Expressions ({{…}}) processed by AngularJS are displayed in their raw (uncompiled) form while the application is loading. The expressions are compiled fast, but even one-tenth of a second will be visible to the user and will make the UI flicker.
AngularJS provides ng-cloak
directive that hides not yet compiled templates until the app loaded. But in the case of RequireJS, it’s not enough, because AngularJS itself can be loaded at the very end. In Appery.io app, AngularJS is loaded using a simple script
tag which is the very first tag on the page. This way we can be sure that AngularJS will be loaded even before CSS styles and RequiresJS. Therefore, all the expressions will be compiled as quickly as possible.
Here is an example of such a page:
html <!DOCTYPE html> <html> <head><title>index</title> <script src="libs/angular/angular.min.js"></script> <!-- ... css style-sheets ... --> <script data-main="app/bootstrap" src="libs/requirejs/require.js"></script> </head> <body> <div ng-controller="indexController" ng-cloak ng-init="init()"> <div ng-view class=""></div> </div> </body> </html>
To prevent AngularJS from being loaded twice by RequireJS, this module was intentionally defined before the first call:
define('angular', [], function () { return window.angular; });
This way we can ensure that all uncompiled UI will be hidden as quickly as possible and that all the required resources will be loaded and ready the moment they are needed.
In the next installment of the “Appery.io under the Hood” series, we’ll cover global app dependencies.