Google Cloud Platform

AngularJS + Cloud Endpoints: A Recipe for Building Modern Web Applications

Introduction

Advances in client-side technologies are driving changes in the role of today’s web application server. Android, iOS and purely browser-based clients are powerful computing platforms in their own right. Their success has led to a reevaluation of ‘classic’ web applications. MVC frameworks, such as AngularJS, have been developed that enable client-side developers to build powerful and compelling UIs.

AngularJS is a popular open source JavaScript client-side MVC framework supporting the rapid development of client-side applications. By implementing a majority of the MVC functionality at client-side, it reduces the complexity of the server and results in applications composed around well-defined APIs that should be more maintainable and applicable.

This paper provides best practices and guidance to web developers who are interested in AngularJS or other client-side MVC technologies and want to optimize their server backend for these technologies. You will learn how to leverage AngularJS with Google Cloud Platform and, in particular, Google Cloud Endpoints, a technology that lets developers expose their backend APIs of Google App Engine applications through service-based API, instantly with simple annotations.

Google Cloud Endpoints makes it easy to build a server-side API and encapsulate the backend boilerplate logic. Incorporating the Google Cloud Endpoints JavaScript client library into AngularJS is so straightforward that you can write a single line of code to call server logic and a callback function to update model properties with the result.

The intended audience for this paper is Java developers with some experience with App Engine and a basic knowledge of JavaScript. Links to the AngularJS and Cloud Endpoint web sites with reference guides are provided in another section and troubleshooting tips are provided in the Appendix. If this is your first time writing Java applications for App Engine, the Getting Started pages on the App Engine site provides a good introduction.

Challenge

A recent and significant change in web application frameworks has been the shift from Model-View-Controller (MVC) on the server to the MVC on the client. Advancements in client-side technology are driving this change. The result, however, is the challenge of building elegant architect applications that span multiple devices hosted in the cloud.

Android, iOS and browser-based clients are rich development platforms and each is able to run a fully-fledged MVC framework. Developing the user interfaces (using MVC) on the client is a more natural architecture that better leverages client developers’ skills. The growing popularity of client-side MVC frameworks such as Backbone.js and AngularJS is representative of this shift to the client. These frameworks help reduce complexity and increase reuse in even the simplest of applications.

As the UI moves to the client, the server becomes simpler. Server-side developers need not worry about how to construct HTML pages using templates to generate the dynamic content. Rather, they can focus on implementing business logic and data persistence using technologies such as App Engine Datastore access and OAuth2 authentication and then use a framework to expose services to their applications’ clients.

The question that remains is how to design and implement these modern web applications. This next section provides some guidance for web application developers and architects wanting to learn how to incorporate browser-based clients into an existing service-based architecture.

Overview: Client-side MVC + Google Cloud Endpoints

Google Cloud Endpoints is the answer for this question: a feature of Google App Engine that provides a RPC framework:

Figure 1: Google Cloud Endpoints

The flow is described in more detail below.

Using Cloud Endpoints, developers expose the public methods of any class as a service endpoint with the addition of simple Java annotations. For example, imagine a simple guestbook application that has a single class “GuestbookEndpointV1” with two methods insert() to add an entry new messages on the guestbook and list() to list them. In order to expose the class as a service endpoint, put the @Api annotation on the class definition:

@Api(name = "guestbook")
public class GuestbookEndpointV1 {
  ...
}

This code would be deployed on the Google App Engine server as shown in Figure 1 above. You will also need to put @ApiMethod annotations on any methods you want to publish to the client.

@ApiMethod(name = "messages.insert")
public void insert(Message message) {
  // store a Message on Datastore
  Entity e = new Entity("Message");
  e.setProperty("createdAt", message.getCreatedAt());
  e.setProperty("createdBy", message.getCreatedBy());
  e.setProperty("content", message.getContent());
  datastore.put(e);
}

The insert() method receives a Java object, message, and comes with properties like createdAt (the creation timestamp), createdBy (the owner of the message) and content. Then it creates a Datastore entity “e” with those property values and saves it onto Google App Engine Datastore.

These service endpoints are automatically exposed via REST API and you can use Google APIs Explorer to view the service endpoints. It is also good to issue test requests to confirm the responses in JSON format. In the following screen, the guestbook.messages.insert results directly from the annotations to the Java class.

Figure 2: Testing your service endpoints with Google APIs Explorer

Combining AngularJS with Cloud Endpoints

The next step addresses the integration of Cloud Endpoints with client-side MVC frameworks such as AngularJS. Such a system would have an architecture similar to the one described in Figure 4:

Figure 4: Client-side MVC and Service Endpoints Architecture

Although the integration between two technologies is straightforward, there are some pitfalls and caveats you should know about before starting your coding. So, let’s take a closer look at the left side of the diagram above (Fig. 4) – the actual JavaScript code that integrates the client-side MVC with the endpoints shown in the right side of the diagram. As an example, let’s consider creating an AngularJS-based web UI for the Guestbook service endpoint we have just defined. The following diagram is a screenshot of how the UI will appear:

Figure 3: Guestbook app with AngularJS + Cloud Endpoints

Creating a View and Model

The message form can be created using an ordinary AngularJS form that calls the controller’s insert() method. The form specifies two model properties, createdBy and content, as shown in the following script:

<h2>Guestbook</h2>
<form ng-submit="insert()">
  <input type="text" ng-model="createdBy" size="50"><br>
  <input type="text" ng-model="content" size="50"><br>
  <input type="submit" class="btn" value="Post">
</form>

The following code demonstrates how you can use an AngularJS iterator to iterate over the messages that will be returned to the client in response to a call to the “list” method on the endpoint:

<ul>
  <li ng-repeat="message in messages">
  {{message.createdAt|date:'short'}} {{message.createdBy}}: {{message.content}}
</ul>

Creating a Controller

The controller’s insert function (set to the $scope.insert variable) corresponds to the insert method of the endpoint we defined. It builds an object that contains the message content and createdAt/createdBy properties copied from the model properties, and then calls the insert method of the endpoint. This is demonstrated in the following JavaScript fragment:

function GuestbookCtrl($scope) {
  $scope.insert= function() {
    message = {
      "createdAt" : new Date(),
      "createdBy" : $scope.createdBy,
      "content" : $scope.content
    };
  ...

Next, you can send the object by passing it to the insert function of the Cloud Endpoint client library. Remember, this is one of the two methods exposed as a Cloud Endpoint with the addition of annotations to the GuestbookEndpointV1 class.

gapi.client.guestbook.messages. insert(message).execute();

In the same way, you can call the service endpoint’s list() method to retrieve messages on the guestbook:

$scope.list = function() {
  gapi.client.guestbook.messages.list().execute(function(resp) {
    $scope.messages = resp.items;
    $scope.$apply();
  });
}

Please note that the anonymous function passed as a callback to the execute() method is called whenever it receives messages from the server,and these would be assigned to the model’s messages property. You will need to call $apply() to apply the model change to the UI, since the callback function is called from outside of the controller thread. Additionally, the Cloud Endpoints client library does not support the Promise API of AngularJS for describing this kind of asynchronous processing.

A tricky aspect when integrating AngularJS with Cloud Endpoints is the initialization sequence. It’s important to know the sequence in which the libraries will be loaded and initialized. If you do not pay attention to the sequence of loading required libraries and their initialization, you could create problems that will take a long time to debug. See the Appendix for further details.

It should be clear from the examples presented that AngularJS and Cloud Endpoints enable quite a straight forward design pattern that combines client-side MVC and server-side service endpoints. In fact, you will find that it is easier and simpler to use Cloud Endpoints for RPC than it is to implement the XHR and Dependency Injection in AngularJS for server communication. Cloud Endpoints also provides many other benefits including integrated OAuth2 security and multi-client platform support for Android and iOS using a shared, standardized client API.

Conclusion

To conclude, here is a summary of some of the key benefits of AngularJS + Cloud Endpoints that were presented in this paper:

  • Cloud Endpoints makes it easy to expose a server-side API by adding annotations to classes and methods to generate client libraries supporting JavaScript.
  • Cloud Endpoints encapsulate the plumbing for OAuth2 authentication, URI definition and request routing, JSON serialization, and RPC with graceful error handling and more. These tasks can be quite non-trivial when implementing them without Cloud Endpoints.
  • The AngularJS JavaScript based client-side MVC framework supports the rapid development of client-side applications. Richuser experiences can be easily implemented by HTML5 and JavaScript. This eliminates the need for server-side HTML rendering.
  • Incorporating the Cloud Endpoints JavaScript client library into AngularJS is so straight forward that you can write a single line of code to call server logic and a callback function to update model properties with the result.

References

Google App Engine, https://developers.google.com/appengine/

AngularJS, http://angularjs.org/

Cloud Endpoints, https://developers.google.com/appengine/docs/java/endpoints/

Appendix: Tips on AngularJS + Cloud Endpoints Initialization

Tip #1: Be careful on the initialization sequence

The guestbook app loads three different JS libraries in the following sequence:

  1. AngularJS
  2. The guestbook app
  3. Google API Client, which contains the Endpoints functionalities

To follow this sequence, the index.html contains the following <script> tags in the <head> tag for loading each of the JS libraries:

<script src="js/angular.min.js"></script>
<script src="js/guestbook.js"></script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>

Once loaded, the third library (Google API Client) calls the initialization function specified by its ‘onload’ parameter. In this case, the init() function is expected and invoked.

Tip #2: Enter into the AngularJS world as quickly as possible

In the initialization sequence, we use the two functions:

  • init() function
  • window.init() function

This init() function is defined in guestbook.js in the following way:

function init() {
  window.init();
}

As you can see the code above, the function just calls window.init() function (i.e. init() function defined in the global window object) and does nothing else. The window.init() is defined in the AngularJS controller as follows:

$window.init= function() {
  $scope.$apply($scope.load_guestbook_lib);
};

In AngularJS, the global window object is accessed by “$window” notation which is a wrapper for it. It is a best practice in AngularJS not to access the window object directly to improve testability.

The reason why you would not want to execute the initialization in the first init() method is so you can put as much of the code as possible in the AngularJS world, such as controllers, services and directives. As a result, you can harness the full power of AngularJS and have all your unit tests, integrations tests,and so forth.

Tip #3: Use a Flag to Indicate If the Backend is Ready

Eventually, the $window.init() is called and you can write any application initialization logic in this function. The primary objective here is to use Google API Client’s onload parameter to invoke an initialization function defined inside theAngularJS controller so that AngularJS can execute all the initialization in a predictable sequence.In the guestbook script, $window.init() calls the load_guestbook_lib() function and is defined as follows.

$scope.load_guestbook_lib = function() {
  gapi.client.load('guestbook', 'v1', function() {
    $scope.is_backend_ready = true;
    $scope.list();
  }, '/_ah/api');
};

No RPC calls should be made until the backend is ready to serve them. The backend’s “readiness” is indicated by property “is_backend_ready”. This property is set in the handler function call back after the guestbook’s endpoints client library loads.To prevent the application logic from calling endpoints before the endpoints are ready, the Guestbook uses a flag named is_backend_ready in the index.html file.

<div ng-controller="GuestbookCtrl" class="container" ng-show="is_backend_ready">
…. guestbook UI...
</div>

By controlling the ng-show attribute with the flag value, the Guestbook UI does not need to be shown until it is ready to make calls to the endpoints.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.