Single Page Applications single-page-applications

NOTE
Adobe recommends using the SPA Editor for projects that require single page application framework-based client-side rendering (for example, React). Learn more.

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 provides an introduction to the concepts behind the SPA and how they use 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 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 its 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 that 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, 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 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, it is 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 contains the HTML from AEM components that have been included on the page, and any AngularJS directives necessary for wiring up the client side of the application. For an example of a AngularJS directive in a Geometrixx component, 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. 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 (at /apps/geometrixx-outdoors-app/components/angular/ng-template-page). Of particular interest is the JavaScript code that it generates when run, 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, the parameter from the $routeParams service is taken, and then massaged into the directory structure in which the JSON data is stored. By dealing with the SKU id in this manner, you 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 waits patiently until the request is finished and the promise it created is fulfilled.

The Next Steps the-next-steps

Once you have learned about the Single Page Applications, see Developing Apps with PhoneGap CLI.

recommendation-more-help
2eeeb575-8007-40cc-a72d-206fbc4ddd4b