AngularJS is a JavaScript-based web application framework. It is used to build single-page web applications for client-sides that use JavaScript, HTML and CSS.
In this post, we will show you how to configure Drupal with AngularJS. This will help you make the best use of features from both platforms in your projects.
Why AngularJS?
For the last couple of years, developers relied on tools such as Adobe Flex, Adobe Flash and JS to build Rich Internet Applications (RIA). Even though these tools were convenient to use, applications built with them had several drawbacks. Some of them are listed below:
- Device incompatibility
- Browser incompatibility
- Junky code that’s difficult to debug
- Large web pages that take a lot of time to load
- Difficulty in building applications using AJAX because you have to deal with HTTPs and browser compatibility issues.
With AngularJS, things are looking a lot brighter.
Google Developers deserve a lot of credit for building a light-weight and scale-able framework on JS and HTML5 that handles all complexities and takes all the pain away from the developer. Developers can now focus more on the functionality they expect while building highly-interactive web-applications.
In Drupal, AngularJS has an able technology ally that’s probably the most revered CMS on earth.
Let’s Get Started!
We assume you have a basic knowledge of Drupal installation, configuration, Drush, themes and module development. A similar knowledge of JS, Twitter Bootstrap, MVC & AngularJS will be beneficial.
First, we need to configure Drupal with AngularJS. There are a few prerequisites we need to install. They are as follows:
Drupal
- Drupal setup (download here)
- AngularJS module (download here)
- Bootstrap module (download here)
- Library module (download here)
- Drush module (download here)
- AngularJS framework (download here)
- All the dependencies related to above modules.
AngularJs
- AngularJS UI Bootstrap (download here)
- jQuery Fullcalender (download here)
- AngularJS directive for Fullcalender (download here)
In case you have any issue installing Drupal7, follow this tutorial.
We are using Drush command to install modules in Drupal:
$ drush dl angularjs $ drush en angularjs $ drush dl bootstrap $ drush en bootstrap $ drush dl libraries $ drush en libraries
Add other modules you wish to install as well.
After you are done installing Drupal and the desired modules, it’s time to install the AngularJS library in Drupal. For that, you need to create a library folder with the following path: sites/all/libraries as shown in the screen-shot below:
And now you need to copy the angular folder inside it as shown in the screen-shot below:
Now, it’s time to visit AngularJS’s configuration section to configure it. A screenshot of the page is shown below:
Now, you can verify whether AngularJS has been installed correctly on your system. Navigate to the link angular/nodes#/. It will show you a basic AngularJS application to prove that everything is working fine. Check the screen-shot below:
There is a very good FireFox add-on called AngScope to inspect the AngularJS application object. It will display the AngularJS scope and objects as in the screen-shot below:
We have now set up Drupal and AngularJS.
Executing A Working Example
We are going to create a Calendar application by using AngularJS and see how we can write a Drupal module to integrate with it.
We have planned a module which is called ng_node_calender. It has an AngularJS application and module files. You can see the screen-shot below for more details:
Module structure:
ng_node_calender/
- ng_node_calender.module
- ng_node_calender.info
- ng_node_calender.install //if required
- themes/* //angular templates file
- js/* //angular app and directives
The JS folder will be home to the AngularJS application. The theme folder will have the Drupal templates files.
Now, we have the directory set up for the module. Let’s start writing our first callback method for the AngularJS application to see how Drupal works with AngularJS.
Module ng_node_calender.info looks like:
name = AngularJS NodeCalender description = It will provide node calender core = 7.x dependencies[] = angularjs version = "7.x-1.3" core = "7.x" project = "ng_node_calender" datestamp = "1404549418"
Module ng_node_calender.module looks like:
/** * Implements hook_menu(). */ function ng_node_calender_menu() { $items = array(); $items['ng_node/calender_app'] = array( 'title' => t('Angular Node Calender'), 'page callback' => 'ng_node_calender', 'access arguments' => array('access content'), ); return $items; }
In the above code, we have registered menu callback to load the AngularJS application inside the Drupal environment.
/** * Page callback for ng_node_calender(). */ function ng_node_calender(){ angularjs_init_application('ng_node_calender'); //CSS files for calender drupal_add_css(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/fullcalendar.css'); drupal_add_css(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/fullcalendar.print.css'); //JS files for calender drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/lib/moment.min.js'); drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/fullcalendar.min.js'); drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/gcal.js'); drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/directives/calendar.js'); drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/directives/ui-bootstrap.min.js'); drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/directives/ui-bootstrap-tpls.min.js'); drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/app.js'); return theme('ng_node_calender'); }
Here, we have registered all the required JS files, which we will be using in our application.
/** * Implements hook_theme * * @return array */ function ng_node_calender_theme() { $themes = array(); $themes['ng_node_calender'] = array( 'template' => 'theme/ng_node_calender', 'parameters' => array(), ); return $themes; }
We have registered template files which actually initialize the application by using ng markups. We have created ng_node_calender.tpl.php inside the theme directory and its code is as follows:
<div id="ng_node_calender" ng-app="ng_node_calender"> <div ng-view></div> </div>
If you look at the angularjs module, you will see a file called angularjs.api.inc. It provides hooks which are given by the angularjs module in order to register AngularJS application directives , controllers and so on.
Now, let us focus on app.js, which actually loads the whole application.
Here is the code:
var app = angular.module('ng_node_calender', ['node', 'nodes','ui.calendar','ui.bootstrap','ui.bootstrap.modal']). config(function($routeProvider) { $routeProvider. when('/', {controller:CalenderCtrl, templateUrl: Drupal.settings.angularjsApp.basePath + '/ng_node/calender/display'}). otherwise({redirectTo:'/'}); });
Drupal.settings.angularjsApp.basePath will contain the base path and CalenderCtrl contains the controller object.
Let us have a look at the CalenderCtrl() function:
function CalenderCtrl($scope, $rootScope,$modal,Nodes,Node){ var date = new Date(); var d = date.getDate(); var m = date.getMonth(); var y = date.getFullYear(); $scope.alerts = []; $scope.changeTo = 'Hungarian'; /* event source that pulls from google.com */ $scope.eventSource = { url: "http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic", className: 'gcal-event', // an option! currentTimezone: 'America/Chicago' // an option! }; /* event source that contains custom events on the scope */ $scope.events = [ {title: 'All Day Event',start: new Date(y, m, 1)}, {title: 'Long Event',start: new Date(y, m, d - 5),end: new Date(y, m, d - 2)}, {id: 999,title: 'Repeating Event',start: new Date(y, m, d - 3, 16, 0),allDay: false}, {id: 999,title: 'Repeating Event',start: new Date(y, m, d + 4, 16, 0),allDay: false}, {title: 'Birthday Party',start: new Date(y, m, d + 1, 19, 0),end: new Date(y, m, d + 1, 22, 30),allDay: false}, {title: 'Click for Google',start: new Date(y, m, 28),end: new Date(y, m, 29),url: 'http://google.com/'} ]; /* event source that calls a function on every view switch */ $scope.eventsF = function (start, end, callback) { var s = new Date(start).getTime() / 1000; var e = new Date(end).getTime() / 1000; var m = new Date(start).getMonth(); var events = [{title: 'Feed Me ' + m,start: s + (50000),end: s + (100000),allDay: false, className: ['customFeed']}]; callback(events); }; $scope.closeAlert = function(index) { $scope.alerts.splice(index, 1); }; $scope.calEventsExt = { color: '#f00', textColor: 'yellow', events: [ {type:'party',title: 'Lunch',start: new Date(y, m, d, 12, 0),end: new Date(y, m, d, 14, 0),allDay: false}, {type:'party',title: 'Lunch 2',start: new Date(y, m, d, 12, 0),end: new Date(y, m, d, 14, 0),allDay: false}, {type:'party',title: 'Click for Google',start: new Date(y, m, 28),end: new Date(y, m, 29),url: 'http://google.com/'} ] }; /* alert on eventClick */ $scope.alertOnEventClick = function( event, allDay, jsEvent, view ){ $scope.alerts.push({msg: 'Another alert!'}); }; /* alert on Drop */ $scope.alertOnDrop = function(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view){ $scope.alertMessage = ('Event Droped to make dayDelta ' + dayDelta); }; /* alert on Resize */ $scope.alertOnResize = function(event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view ){ $scope.alertMessage = ('Event Resized to make dayDelta ' + minuteDelta); }; /* add and removes an event source of choice */ $scope.addRemoveEventSource = function(sources,source) { var canAdd = 0; angular.forEach(sources,function(value, key){ if(sources[key] === source){ sources.splice(key,1); canAdd = 1; } }); if(canAdd === 0){ sources.push(source); } }; /* add custom event*/ $scope.addEvent = function() { $scope.events.push({ title: 'Open Sesame', start: new Date(y, m, 28), end: new Date(y, m, 29), className: ['openSesame'] }); }; /* remove event */ $scope.remove = function(index) { $scope.events.splice(index,1); }; /* Change View */ $scope.changeView = function(view,calendar) { calendar.fullCalendar('changeView',view); }; /* Change View */ $scope.renderCalender = function(calendar) { if(calendar){ calendar.fullCalendar('render'); } }; /* config object */ $scope.uiConfig = { calendar:{ height: 450, editable: true, eventStartEditable: false, header:{ left: 'prev,next today', center: 'title', right: 'month,basicWeek,basicDay' }, eventClick: $scope.alertOnEventClick, eventDrop: $scope.alertOnDrop, eventResize: $scope.alertOnResize, } }; $scope.changeLang = function() { if($scope.changeTo === 'Hungarian'){ $scope.uiConfig.calendar.dayNames = ["Vasárnap", "Hétfő", "Kedd", "Szerda", "Csütörtök", "Péntek", "Szombat"]; $scope.uiConfig.calendar.dayNamesShort = ["Vas", "Hét", "Kedd", "Sze", "Csüt", "Pén", "Szo"]; $scope.changeTo= 'English'; } else { $scope.uiConfig.calendar.dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; $scope.uiConfig.calendar.dayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; $scope.changeTo = 'Hungarian'; } }; /* event sources array*/ $scope.eventSources = [$scope.events, $scope.eventSource]; }
This controller function is the implementation of angular-ui calendar directive, which can be found here.
So let’s see how it works. We have a menu callback ng_node/calender_app. Hitting it will trigger a callback method called ng_node_calender, which loads and initializes the AngularJS application with all the dependencies.
The question is how will AngularJS controller be called? How will it load Fullcalender AngularJS directives? We know that an AngularJS directive is required to include markup inside template file. How can we achieve this inside Drupal menu callback and with templates files?
Here is the answer to all these questions. We need to create another menu callback which will load the partial view of AngularJS inside Drupal templates. Let’s have a look at it:
function ng_node_calender_menu() { $items = array(); /** * @todo: Create menu callback which load angular js calender */ $items['ng_node/calender_app'] = array( 'title' => t('Angular Node Calender'), 'page callback' => 'ng_node_calender', 'access arguments' => array('access content'), ); $items['ng_node/calender/display'] = array( 'title' => t('Display calender'), 'page callback' => 'ng_node_calender_display', 'access arguments' => array('access content'), ); return $items; }
By hitting ng_node/calender/display through the AngularJS application, it will trigger ng_node_calender_display, which will load a partial view through the Drupal template system. We have used <div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>, which is responsible for loading AngularJS directives for Calendar. Look at the code below to understand better:
function ng_node_calender_display(){ return drupal_get_form('calender_display_form_builder'); } function calender_display_form_builder($form, &$form_state){ $form['calender_display'] = array( '#type' => 'item', '#ng_controller' => 'CalenderCtrl', '#markup' => '<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>' ); return $form; }
If you look at the above code, you will notice #ng_controller => CalenderCtrl. This property is provided by the angularjs module so that we can bind controller class through Drupal Form API. In this way, angularjs will know we have attached a controller with this element. As there are other properties also provided by that module (fpr eg., #ng_model), you can also add more AngularJS properties by using the #attributes property of FORM API.
Here is the screen-shot of the final result:
Conclusion
We have covered the basic techniques to show you how to use Drupal Framework with AngularJS Framework with adherence to Drupal Standards. Hope this post helps you with your Drupal-Angularjs integration project!
Comments
Thanks for the tutorial. I
Thanks for the tutorial. I have tried it on a new drupal install.
With angularJs 1.2.x . The calendar is not loading.
Can you please provide the zip of your project.
When i go to ng_node/calender/ all the js files are loaded but not calendar is showing.
When i go to ng_node/calender/display none of the Js files is loaded.
Thanks
Could you Please Share the
Could you Please Share the full folder structure for ng_node_calender module here
Calender vs Calendar
One small note, the correct spelling is "calendar", not "calender."
Thanks!
Thanks for the feedback! :)
Nice Article
Nice article Neerav
The article is too easy to
The article is too easy to understand. I have followed the steps and trying to work more.
Thanks for the article.
Use Drupal commerce api in standalone angular js application.
I want to use drupal commerce as back-end.
and angular js as standalone ui application. How can I use drupal commerce api's in standalone angular js application.
Commerce and Angular
Did you ever find any info about this? I'm considering a headless Drupal Commerce website with an Angular frontend, I'm just not sure if Commerce can do that without a lot of engineering.