Melding AngularJS With Drupal Sites
After spending the weekend going through a rather excellent (I thought) video overview course on AngularJS, my head was spinning with the possibilities when it came to building Drupal sites that were much more responsive in terms of loading and displaying content. So, I decided to start working on some proof-of-concept modules that would help me better understand how to effectively meld these two tools together.
I worked up my first example module where I use AngularJS to load information in a block on a page. The information is a listing of Articles, which I display as an unordered list. Each list item has a button that, when clicked, opens the article title and teaser, along with a thumbnail of the article's image, in a dialog, with a link to the full node page at the bottom of the dialog.
My starting point was an example I found using my google-fu, which is published on SitePoint. While it is a decent working example that utilizes a few of the Angular concepts, I decided to refactor it to make it more practical as a working Drupal example and to also leverage Angular's capabilities more effectively.
So, I cloned the author's sample code from Github, and installed it as a module on a sandbox Drupal site, created an Article content type, and used the Devel module to generate some sample data with which to work. I also moved the ngDialog scripts, included with the author's sample code, into the module folder (in a more practical application I would place these in sites/all/libraries, but I wanted to keep all the code I was using together in one place for now), and tweaked his code to reference that location.
I had to make a few more minor tweaks to the author's code to use the specific field names that I chose when I created my content type, but other than it was pretty simple to get up and running. The author's code generates a block that is available at admin/structure/blocks for use on the site. I placed this block so that it would display on the on the /user page on my sandbox site, verified that my article listing was showing up and that the Filter was working. Now, time to refactor.
Refactor 1: Install the Views Datasource module and create some Views returning JSON data
The first thing I did in preparation for refactoring the original module code was to install the Views Datasource module. This module has a submodule, Views JSON, that will allow you to turn any Views page into a RESTful service, returning JSON data at the endpoint. Cool!
Next, I constructed a couple of views which I am using to replace the author's not so elegant database queries. This allows me to leverage Drupal's built-in permissions as well as the power of Views to query my data and return my results. I built two views: one pulling an entire list of all articles, and another that returned the contents of a single article using a contextual filter on the node ID. The author had three database queries total in his module; however, I decided to leverage Angular's data binding and filtering capabilities to filter my data instead, so these two JSON feeds were all that were needed to replace the author's database queries.
Refactor 2: Change the API callback to use my Views results
The author had written a function into this module, ang_node_api(), that took a single optional parameter. This function is a page callback for a hook_menu() item and, based on the parameter passed in, runs one of three database queries that return node information that is used by the Angular app included with his code.
I refactored the code in this function to instead make a drupal_http_request() to the Views page URLs from my two views, which are serving as my RESTful endpoints. I removed the author's "elseif" component of his "if" statement since my plan is to replace that completely with an AngularJS directive.
Now, I'm using json_decode() on the results returned by the drupal_http_request(), in my code currently, but a future exercise might be to write an Angular service to consume that data instead (an exercise for a later date).
Refactor 3: Change the Angular app code
Surprisingly, the Angular app code that the author originally included in his example code needed very little changing. I did two things: wrapped the code in an anonymous function to get it out of the global scope and removed the callback to perform the search when the user types in the text to filter on the article title. We will be setting up our template to leverage Angular for that search/filter capability instead.
Refactor 4: Change angular-listing.tpl.php to leverage Angular's data binding and filtering capabilities
My last refactoring was to change angular-listing.tpl.php to bind to our results data and filter our data via Angular instead of querying the database via Drupal. I basically used ng-model to bind my input type to my list item (which is looped through and repeated using Angular's ng-repeat directive), and further changed the list items to order the list by node title, and to limit the displayed results to 150. I also changed the dialog box to display the article image and added a link to the node page for the article after the display of the node body teaser.
I placed my block on the /user page, cleared the cache, navigated to the user page and Wah Lah! There's my filterable article listing, complete with dialog popup (using Angular's ng-template directive and ngDialog).
I posted my module code out on GitHub. I created features containing the Article content type and the Views and included those as well so that you have everything if you are interested in downloading and playing with the building blocks I have so far.
Please, feel free to leave me to feedback here if you have changes or improvements, or fork the repo and make a pull request. Overall, I am excited about the way that Angular works when melded with Drupal, and I see so many possibilities for making client projects better.