Single Page Applications single-page-applications
Single-Page Applications (SPA) have reached critical mass, widely regarded as the most effective pattern for building seamless experiences with web technology. By following a SPA pattern, you can create an application that performs identically to a desktop or mobile application, but reaches a multitude of device platforms and form factors due to its foundation in open web standards.
Generally speaking, SPAs appear more performant than traditional page-based web sites because they typically load a complete HTML page only once (including CSS, JS, and supporting font content), and then load only exactly what is necessary each time a change of state occurs in the app. What’s necessary for this change of state can vary based on the set of technologies chosen, but typically includes a single HTML fragment to replace the existing ‘view’, and the execution of a block of JS code to wire up the new view and perform any client side template rendering that may be necessary. The speed of this state change can be improved even further by supporting template caching mechanisms, or even offline access to template content if Adobe PhoneGap is used.
AEM 6.1 supports the building and management of SPAs via AEM Apps. This article will provide an introduction to the concepts behind the SPA and how they leverage AngularJS to bring your brand to the App Store and Google Play.
SPA in AEM Apps spa-in-aem-apps
The Single-Page Application framework in AEM Apps enables the high performance of an AngularJS app, while empowering authors (or other non-technical personnel) to create and manage the app’s content via the touch-optimized, drag-and-drop editor environment that has traditionally been reserved for managing web sites. Already have a site built with AEM? You’ll find that reusing your content, components, workflows, assets, and permissions is easy with AEM Apps.
AngularJS Application Module angularjs-application-module
AEM Apps handles much of the AngularJS configuration for you, including putting together your app’s top-level module. By default this module is named ‘AEMAngularApp’ and the script responsible for it’s generation can be found (and overlaid) at /libs/mobileapps/components/angular/ng-page/angular-app-module.js.jsp.
Part of the initialization of your app involves specifying which AngularJS modules the app depends upon. The list of modules used by your app is specified by a script located at /libs/mobileapps/components/angular/ng-page/angular-module-list.js.jsp, and can be overlaid by your own apps’ page component to pull in any additional AngularJS modules that your app requires. As an example, compare the above script with the Geometrixx implementation (located at /apps/geometrixx-outdoors-app/components/angular/ng-geometrixx-page/angular-module-list.js.jsp).
To support navigation between the distinct states in your app, the angular-app-module script iterates through all the descendant pages of your top level app page to generate a set of ‘routes’ and configures each path on Angular’s $routeProvider service. For an example of how this looks in practice, take a look at the angular-app-module script generated by the Geometrixx Outdoors app sample: (link requires a local instance) http://localhost:4502/content/phonegap/conference-app/en/home.angular-app-module.js
Digging in to the generated AEMAngularApp, you will find a series of routes specified as follows:
$routeProvider
.when('/content/phonegap/geometrixx-outdoors/en/home/products/:id', {
templateUrl: 'home/products.template.html',
controller: 'contentphonegapgeometrixxoutdoorsenhomeproducts'
})
The above sample in particular illustrates an example of passing a parameter as part of the path. In this example we are indicating that when a path that meets the pattern specified (https://experienceleague.adobe.com/content/phonegap/geometrixx-outdoors/en/home/products/:id?lang=en) is requested, it should be handled by the home/products.template.html template and use the ‘contentphonegapgeometrixxoutdoorsenhomeproducts’ controller.
The template to load when this route is requested is specified by the templateUrl property. This template will contain the HTML from AEM components that have been included on the page, as well as any AngularJS directives necessary for wiring up the client side of the application. For an example of a AngularJS directive in a Geometrixx component, take a look at line 45 of the swipe-carousel’s template.jsp (https://experienceleague.adobe.com/apps/geometrixx-outdoors-app/components/swipe-carousel/template.jsp?lang=en).
Page Controllers page-controllers
In Angular’s own words, “a Controller is a JavaScript constructor function that is used to augment the Angular Scope.” (source) Each page in an AEM App is automatically wired up to a controller that can be augmented by any controller which specifies a frameworkType
of angular
. Take a look at the ng-text component as an example (https://experienceleague.adobe.com/libs/mobileapps/components/angular/ng-text?lang=en), including the cq:template node which makes sure each time this component is added to a page it includes this important property.
For a more complex controller example, open up the ng-template-page controller.jsp script (located at /apps/geometrixx-outdoors-app/components/angular/ng-template-page). Of particular interest is the javascript code it generates when executed, which renders as follows:
// Controller for page 'products'
.controller('contentphonegapgeometrixxoutdoorsenhomeproducts', ['$scope', '$http', '$routeParams',
function($scope, $http, $routeParams) {
var sku = $routeParams.id;
var productPath = '/' + sku.substring(0, 2) + '/' + sku.substring(0, 4) + '/' + sku;
var data = $http.get('home/products' + productPath + '.angular.json' + cacheKiller);
/* ng-product component controller (path: content-par/ng-product) */
data.then(function(response) {
$scope.contentparngproduct = response.data["content-par/ng-product"].items;
});
/* ng-image component controller (path: content-par/ng-product/ng-image) */
data.then(function(response) {
$scope.contentparngproductngimage = response.data["content-par/ng-product/ng-image"].items;
});
}
])
In the above example, you will note that we are taking a parameter from the $routeParams
service and then massaging it into the directory structure that our JSON data is stored in. By dealing with the sku id
in this manner, we are able to deliver a single Product template that can render the product data for potentially thousands of distinct products. This is a far more scalable model that requiring an individual route for each item in a (potentially) massive product database.
There are also two components at work here: ng-product augments the scope with the data it extracts from the above $http
call. There is also an ng-image on this page which in turn also augments the scope with the value it retrieves from the response. By virtue of Angular’s $http
service, each component will wait patiently until the request is finished and the promise it created is fulfilled.
The Next Steps the-next-steps
Once you have learnt about the Single Page Applications, see Developing Apps with PhoneGap CLI.