Move logic to the front end with AngularJS

How to speed up Drupal by migrating server-side logic to the browser

At Lullabot, we always aim to make sites as performant and maintainable as possible. Recently, we've started to decouple bits of logic from Drupal and move them to the client's browser using JavaScript.

Let's look at an example. We want to display the weather of a given city in our website. This involves:

Calling a public API with some parameters. We have chosen OpenWeatherMap for this example.

Extract weather data from the response.

Show the data in the browser.

The result would look like the following:

In Drupal, we could create a block that uses drupal_http_request() to fetch the data, then passes its results to a theme function that renders it. That is simple and maintainable, but why does Drupal needs to take care of this? There is no database involved, nor session management. If our site relies on caching to improve performance, we'll have to clear that cache whenever the block's content is updated.

Instead, let's move this to pure JavaScript and HTML so the client's browser will be the one in charge of fetching, processing and caching the data.

Meet AngularJS

AngularJS is an MVC JavaScript framework which elegantly separates controller, business and model logic in your application. Although there is a lot to learn, it removes a lot of backend logic in our Drupal projects and we've had wonderful success with it so far.

The attribute data-ng-app="myapp" is telling AngularJS to bootstrap our application named "myapp". For the moment this is all we need, so let's move on. We will implement our AngularJS application later.

Rendering the skeleton in Drupal

Our custom Drupal module contains some simple code that implements a block. The mymodule_block_view() function also includes a JavaScript file (the AngularJS controller) and a template which holds the markup that the AngularJS controller will use:

There you have it! We have a fully functional block that is processed in the browser. If we apply this pattern to other frequently-changing blocks on the page, we'll be able to simplify the work that Drupal does, make the page's caching more efficient, and achieving better performance. You can even use this pattern to lazy load content that varies from user to user, making the rest of the page easier to cache.

On consuming external APIs

Whenever you are building a page on one domain and requesting data from another in the client's browser, remember that browser security mechanisms can sometimes stand in the way. There are two popular ways of overcoming this. One is through Cross-Origin Resource Sharing: of course, there is a module for that on Drupal.org. The other method is using JSONP. That's the method we used in this example, and it is supported by AngularJS and JQuery.

Why not build it with jQuery?

Technically, it is possible to build the same functionality using JQuery. However, it would require more code: you would have to take care of hiding the template while the page is being built, define a listener for the submit button, sanitize data, and bind it to the template yourself. Even with such a simple example, AngularJS offers a simple, more structured approach. It's also possible to use jQuery within AngularJS code.

yakoub

this is clearly bad writing by most basic standards, and i don't understand why javascript community continue to adopt this style .

first you can't document such code .

second you have parenthesis that ends one inside the other at the
end of file in very ugly way which make syntax unreadable

third it creates dependency in different sections of unrelated code,
for example if you have error in function body then it is difficult to reach and identify it using debugger because you need to enter other layers of wrapping function calls and object instantiating .

yakoub

i believe people used to write this style of one line code because they used to do very simply tasks in javscript .
but if you intend to write complex logic like you declare then you can't continue using that same one line code style .

AngularJS is still the hot "new" thing although it's not really new. By a coincidence, I was doing a bit of personal development on a simple html/angularjs/bootstrap3 app where I didn't want any back end and simple content injection.

I was trying to figure out how to create dynamic HTML without having to write a butt load of javascript to do it.

In the end I chose to use showdown/bt-markdown to inject content, and wrote the equivalent of theme_link to return either a link in markdown or a h3, if no href present. That way I didn't have to use the dreaded "unsafe" directive.

It seemed to me like I was trading off "blobs" in my content for "blobs" in my partials as partials just seem to end up looking like the output of WYSIWYG editors or nasty templates. I think as I keep learning Angular I might have to just write the equivalent of Views or something to keep my partials as simple and re-usable as possible.

One of the interesting advances that's coming in Drupal 8 is the use of the Twig templating system for Drupal themes. There's an implementation of Twig in JS in addition to PHP -- I'm curious whether that could lead to a world where rendering a chunk of content would use the same templates on the client AND the server side.

Chris

I am unconvinced that the amount or complexity of equivalent jQuery code for this feature would be sufficient to warrant adding Angular to the mix. I suspect that a much better example could have been presented to make the case.

The main problem I find with this jQuery implementation is the lack of separation between logic and markup. This makes the code more verbose; having to define longer ids for each form element and then using long selectors to set or obtain their values.

AngularJS also takes care of logging errors (console.log() may not be available in some browsers). In JQuery, using JSONP, if the request fails, you seem to have to chance to catch the error (or at least I was not able to while writing the example).

I've used angular myself for simplifying javascript functionality in Drupal projects. I just learned though that Drupal 8 will be shipping with Backbone. It makes me wonder if we should start getting used to the incumbent Backbone instead of the edgy Angular. What are your thoughts? Could they conflict?

The main idea of the article is how useful can be to decouple dynamic data from Drupal rendering.. so we can take advantage of Cache, and in the other hand, the power of libraries like AngularJS

The example is just that, an example. Probably is overkill to use a full framework for such a simple form. But in the moment you start to add more data, UI dependencies, events, etc, you will appreciate to have a JS framework helping you.

To complete the article I created jsfiddles with your examples, and a Backbone.js one, for comparison:

Paul Mackay

Hi, I'm looking at an Angular/Drupal integration, but I need to save some data created by the Angular app back to Drupal, e.g. to create a new node. Do you have any suggestions of what would be the best approach for doing that?

If you want to alter the data in Drupal from your AngularJS application then you either need to open up this node creation to the general public, or you need a mechanism to authenticate users. You could implement cookie-based authentication along with something like Drupal Services module or RESTful Web Services module (which supports HTTP Basic authentication). Alternatively, you could implement OAuth three-legged-authentication using OAuth module.

Cookie-based authentication means implementing a login form in your application that sends the user credentials to Drupal, which returns a session id if the authentication was successful. Once you get the session id you can add it into the header of a request that, for example, creates a node on the backend Drupal website.

Using OAuth three-legged-authentication means setting up OAuth module as an authentication server so when the user clicks Login in your AngularJS application it is redirected to Drupal to authenticate himself and, if successful, returns to the AngularJS application with an OAuth token which will be used in the following request to create content.

The biggest concern here is security. Make sure you have appropriate restrictions on who can and cannot perform these operations and also use HTTPS protocol.

Paul Mackay

Thanks Juampy! If the Angular code is just one part of the Drupal page, and the user is already logged in, can the Angular code get hold of and send back the session ID already created, without needing a separate login mechanism via REST or similar?

Drupal uses HttpOnly flag when setting the cookie. Most browsers support this setting, which makes the session cookie to be unreadable by scripts. Is it a safety measure, since otherwise not only your code, but also attacking scripts, could hijack your session cookie and use it for their own benefit.

It seems that you can enable the cookie to be readable by adding ini_set('session.cookie_httponly', '1'); to your settings.php. Note that this opens a security whole though.

An alternative would be to set a token when you are authenticated in the JavaScript Drupal.settings object so that AngularJS could use it to authenticate requests.

You can define as many of these [modules with their controlers] as you need and they can be totally independent one from another.

Totally independent? Don't they all need to know about and reference the string "myapp"? This is the fundamental problem that I see about distributing AngularJS plug-n-play modules.

The documentation does mention that you can have multiple applications but you cannot auto-bootstrap them (you have to call angular.bootstrap which comes with its own tradeoffs) and that you cannot nest them.