Binding Drupal Data with AngularJS: A Step by Step Tutorial

AngularJS is a popular javascript framework backed by Google. This article will guide you through setting up a simple Angular app using data generated by Drupal. If you’re new to Angular, you may like:

Why Does Angular.js Rock?

PhoneCat Tutorial App

What you’ll need for this tutorial:
Drupal
jQuery Update
Libraries
AngularJS
Views
Views Datasource

This tutorial assumes a decent knowledge of Drupal. Our app will be a collection of code snippets (using the default ‘Article’ content type) filtered by ‘section’ taxonomy (say PHP snippets, CSS snippets, etc.) - Live Demo

 

First enable the contrib modules:

at /admin/modules

 
 
 
 

Now let’s configure these modules:

/admin/config/development/angularjs

(we are using CDN here for ease of use, but we would want to have this locally on production)

 

/admin/config/development/jquery_update

 

Now let’s create a custom module called “sections” and place it in your sites/all/modules/custom

This is what it will end up looking like:

 

create a sections.info

name = Sections description = Angular sections version = 7.x-1.0 core = 7.x

Now enable the “sections” custom module at at /admin/modules

Create sections.module

In there we will add page callback and add our js dependencies:

function sections_menu() {
  $items['sections'] = array(
    'page callback' => 'all_sections_page',
    'access arguments' => array(
      'access content'
    ) ,
  );
  return $items;
};
/**
 * hook_theme()
 */

function sections_theme() {
  return array(
    'all_sections' => array(
      'template' => 'all-sections',
    ) ,
  );
}

/**
 * All sections callback
 */

function all_sections_page() {
  $path = Drupal_get_path('module', 'sections');
  Drupal_add_js($path . '/js/sections.gen.js');
  return theme('all_sections');
}

We are now calling a template file so we need to create it:

in all-sections.tpl.php

<div id="sections-app" class="ng-container">
  <div ng-view="" class="anim"></div>
</div>

What we are doing here is defining a simple container for our app. The important thing here is the ID “sections-app.” In regular AngularJS apps, we would have used ng-app=sections-app instead, but in this case - and because we are using Drupal - we can avoid some issues by bootstrapping with jQuery, which is native to Drupal anyway. The ng-view defines the container for our html files within the “templates” folder.

Before we set up our Angular app, let’s set up the back end that will build our Angular-ready data, basically outputting the Drupal data as Json with views and views data source.

Data

1) A taxonomy type called “Section” (machine name being section)

2) The article content type, which we modify as such:

 

3) Our first view:

 

Pretty basic as well. A page with a path we need for our controller, the fields from taxonomy, and a simple filter. The magic resides in the format obviously, provided by the views datasource modules. It will allow us to expose the data as json to the browser (no rendering).

 

The root object name is pretty important here. We can strip the markup or not, it really depends on the kind of content we have, but eventually it may modify the way we render the data with or without ng-render.

 

Notice you’ll have to uncheck the views API mode checkbox.

4) Our second view

 

This one is for the ‘article content type.’ Here the path takes an argument, defined by a contextual filter.

 

That is the main difference, except that we also need to make sure that we don’t strip the html in our json config and use the ‘node’ root object name.

 

Pretty simple.

The angular code

1) The app

in sections.gen.js

'use strict';

var
  sectionsApp = angular.module(
    'sectionsApp', [
      'ngRoute',
      'ngSanitize',
      'ngAnimate',
      'sectionsDirectives',
      'sectionsControllers'
    ]);

sectionsApp.config([
    '$routeProvider',

    function($routeProvider) {

      $routeProvider

        .when(
          '/', {
            templateUrl: '/sites/all/modules/custom/sections/templates/sections.html',
            controller: 'sectionsCtrl'
        })
        .when(
          '/section/:tid', {
            templateUrl: '/sites/all/modules/custom/sections/templates/articles.html',
            controller: 'articlesCtrl'
        })
        .otherwise({
          redirectTo: '/'
        });
    }
]);

jQuery(document).ready(function() {
  angular.bootstrap(document.getElementById('sections-app'), [
    'sectionsApp'
  ]);
});

First we define the app dependencies, then our routing; pointing to our templates and controllers; then the jQuery call to initiate the app (as explained above). Nothing fancy here, just the basics.

Notice the '/section/:tid' which is our taxonomy ID, which basically acts like an argument when using views conditional filters (in combination with the controller we will define below).

2) The Controllers

in sections.gen.js, below the previous code

'use strict';

var sectionsControllers = angular.module('sectionsControllers', []);
  
  sectionsControllers
    .controller(
      'sectionsCtrl', [
        '$scope',
        '$http',
        '$location',
        
        function($scope, $http, $location) {
          
          $http
          .get(
            '/json/sections'
          )
          .success(
            function(result) {
              $scope.sections = (
                function() {
                  return result.taxonomy;
              })();
          });
        }
      ])
          
    .controller(
      'articlesCtrl', [
        '$scope',
        '$routeParams',
        '$http',
        '$sce',
        
        function($scope, $routeParams, $http, $sce) {

          $http
          .get(
            '/json/' + $routeParams.tid + '/articles'
          )
          .success(
            function(result) {
              $scope.renderHtml =
              function(htmlCode) {
                return $sce.trustAsHtml(htmlCode);
              };
              $scope.articles = (
                function() {
                  return
                  result.node;
              })();
          });
        }
      ]);

 

Here we are setting up our two controllers that will fetch the Drupal data. The second one includes the $routeParams variable that will interact with the contextual filter we set up in the second view (taxonomy id). It also includes the $sce.trustAsHtml(htmlCode) which will render the html from the json safe.

Now to the templates

create sections.html and place it in the “templates” folder

<div class="section">
  <div class="search-section">
    <div class="wrapper">
      <input ng-model="query" placeholder="Quick Search" class="text" id="search">
    </div>
  </div>
  <ul class="ngdata">
    <li data-ng-repeat="section in sections | filter:query | orderBy:'name'" ng- class -odd="'odd'" ng- class -even="'even'" ng- class="{'first':$first, 'last':$last}">
      <a href="#/section/{{section.tid}}" class="section-link">
        <div class="wrapper">
          <h2>{{section.name}}</h2>
          <div class="description">{{section.description}}</div>
        </div>
      </a>
    </li>
  </ul>
</div>

 

create articles.html and place it in the “templates” folder

<div class="article">
  <div class="search-section">
    <div class="wrapper">
      <input ng-model="query" placeholder="Quick Search" class="text" id="search">
    </div>
  </div>

  <ul class="ngdata">
    <li ng-repeat="article in articles | filter:query | orderBy:'title'" ng- class -odd="'odd'" ng- class -even="'even'" ng- class="{'first':$first, 'last':$last}" prism="">
      <div class="wrapper">
        <div class="articles">
          <h2>{{article.title}}</h2>
          <div class="description" ng-bind-html="renderHtml(article.body)"></div>
        </div>
      </div>
    </li>
  </ul>
</div>

The difference between both is mostly the way we render the html with ng-bind-html and the function we wrote in the controller.

That’s it, it should create a basic working version of the demo, minus the theming and a couple of other functions. Of course you’ll have to populate the content by creating articles filtered with the taxonomy term of your choosing. I also have some directives setup in the online demo, but these are calling scripts to highlight the code with prism and the keyboard navigation which are not needed for the purpose of this tutorial.
There is much to improve and once we get a lot of entries it would probably be important to to scale the app as the performance could be affected.

Got help from many sources online and especially this video tutorial.

Hope you found this helpful!