Error message

  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Notice: Trying to access array offset on value of type int in element_children() (line 6609 of /home1/tylerfra/public_html/includes/common.inc).
  • Deprecated function: implode(): Passing glue string after array is deprecated. Swap the parameters in drupal_get_feeds() (line 394 of /home1/tylerfra/public_html/includes/common.inc).

Headless Drupal with Angular JS and Bootstrap - Hello World

Category: 

This tutorial describes how to build a very simple de-coupled Drupal web application powered by Angular JS and Bootstrap. The inspiration for writing this tutorial came after completing my first Angular JS module (angular-drupal), which of course is for Drupal!

To keep things simple, and in the spirit of "Hello World", the application will let us login using credentials from the Drupal website, and then say hello to the user upon successful login.

The complete code for this example app is available here: https://github.com/signalpoint/headless-drupal-angular-bootstrap-hello-w...

Ready? Alright, let's go headless...

Prerequisites

Primary Technologies Involved

0. Clone the Angular Seed Project

Once you have the prerequisites mentioned above installed, you can git clone (or download and extract it to /var/www/headless-drupal) the project:

https://github.com/angular/angular-seed

cd /var/www
git clone --depth=1 https://github.com/angular/angular-seed.git headless-drupal
chgrp -R www-data headless-drupal
chmod g+s -R headless-drupal
cd headless-drupal
sudo npm install
bower install

1. View the Angular Seed Project in a Browser

We should now be able to view the Angular Seed Project in our browser, just navigate to:

http://localhost/headless-drupal/app

2. Add jQuery and Bootstrap to the Project

Open the /var/www/headless-drupal/bower.json file and add bootstrap to the end of the dependencies object:

"bootstrap": "~3.1.1"

Then run the bower install command again:

bower install

This command will install bootstrap, and its dependency jquery.

Next, include the bootstrap css file, and the jquery and bootstrap javascript files in the index.html file right before the closing <head> tag:

<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>

3. Add angular-drupal to the Project

Open the package.json file and add angular-drupal to the devDependencies:

"angular-drupal": "^0.0.2"

Then install this new dependency by running npm install again:

sudo npm install

Then include the angular-drupal javascript file in the index.html file immediately after the other angular javascript files in the <body> tag, and before the app.js file:

<script src="../node_modules/angular-drupal/build/angular-drupal.min.js"></script>

Unfortunately another bower project is using the angular-drupal namespace, so we can't use bower for this, le sigh.

4. Install Drupal Alongside the App

cd /var/www/headless-drupal
wget http://ftp.drupal.org/files/projects/drupal-7.38.tar.gz
tar -zxvf drupal-7.38.tar.gz
rm drupal-7.38.tar.gz
mv drupal-7.38/ drupal
mysql -u root -p
create database headless_drupal;
exit;
cd drupal/sites/default
mkdir files
cp default.settings.php settings.php
chmod 775 settings.php

Now we can install Drupal by visiting the following URL in our browser:

http://localhost/headless-drupal/drupal

After the installation, remove the write permissions from the settings.php file:

chmod 444 settings.php

5. Install and Setup the Services Module for Drupal

drush dl services
drush en -y rest_server

Then create a new endpoint by going to admin/structure/services/add with the following info:

machine name: api
server: REST
path: api
debug: unchecked
session authentication: checked

Then click the edit resources link and check the box next to each resource (at minimum we must enable the user login resource for this demo):

comment
file
node
system
taxonomy_term
taxonomy_vocabulary
user

Then click Save. After that, click the Server tab and make sure the following boxes are checked:

json
application/json
application/x-www-form-urlencoded

Then click Save. After that flush all of Drupal's caches.

drush cc all

6. Create a User Login Form

Hop into the app directory, then make a folder called user_login, along with two files user_login.html and user_login.js:

cd app
mkdir user_login
cd user_login
touch user_login.html
touch user_login.js

Open the user_login.html file, paste this code into it and save the file:

<div ng-controller="UserLoginCtrl" class="container">

  <form novalidate class="form-signin simple-form">
    <h2 class="form-signin-heading">Please sign in</h2>
    <label for="edit-name" class="sr-only">Username</label>
    <input type="text" ng-model="user.name" id="edit-name" class="form-control" placeholder="Username" required autofocus>
    <label for="edit-pass" class="sr-only">Password</label>
    <input type="password" ng-model="user.pass" id="edit-pass" class="form-control" placeholder="Password" required>
    <button class="btn btn-lg btn-primary btn-block" type="submit" ng-click="submit(user)" >Login</button>
  </form>

</div> <!-- /container -->

The html for this page was borrowed from the page source of the Signin Template for Bootstrap, and then converted into an Angular JS Form.

Then open the user_login.js file, paste this code into it and save the file:

'use strict';

angular.module('myApp.user_login', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/user/login', {
    templateUrl: 'user_login/user_login.html',
    controller: 'UserLoginCtrl'
  });
}])

.controller('UserLoginCtrl', ['$scope', 'drupal', function($scope, drupal) {
      $scope.submit = function(user) {
        drupal.user_login(user.name, user.pass).then(function(data) {
            alert('Hello world and hello ' + data.user.name + '!');
        });
      };
}]);

Notice how we use Angular's dependency injection on the UserLoginCtrl to include drupal, which is a service provided by the angular-drupal module.

Then open the index.html file and replace the entire ul menu with this:

<nav class="navbar navbar-inverse navbar-fixed-top">
  <div class="container">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">angular-drupal</a>
    </div>
    <div id="navbar" class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <li><a href="#/view1">view1</a></li>
        <li><a href="#/view2">view2</a></li>
        <li><a href="#/user/login">Login</a></li>
      </ul>
    </div><!--/.nav-collapse -->
  </div>
</nav>

Then include the user_login.js file before the closing <body> tag of the index.html file:

<script src="user_login/user_login.js"></script>

Finally include the myApp.user_login and angular-drupal modules in the app.js file:

'myApp.user_login',
'angular-drupal'

And then add the angular-drupal configuration to the bottom app.js file:

// Angular Drupal Configuration Settings
angular.module('angular-drupal').config(function($provide) {
    $provide.value('drupalSettings', {
        sitePath: 'http://localhost/headless-drupal/drupal',
        endpoint: 'api'
    });
});

7. Add some CSS

Open the app.css file and add some simple styles:

div[ng-view] {
  margin-top: 60px;
}
form input {
  margin-bottom: 0.5em;
}

8. Logout of the Drupal Website

Since our Drupal site lives in the same domain as the app, they will share session information. In order to test the user login form in the app, we must log out of Drupal. Do so now, or forever hold your peace.

9. Run the App

Now if we reload the app, and navigate to the Login page we can then enter our credentials for Drupal user #1:

And if all goes well, we can login and a nice machine will say hello world to us!

10. Conclusion

Well that was fun, wasn't it? From here it's up to you how the app takes shape. The angular-drupal module is there to easily handle reading/writing entities to/from Drupal, as well as handling user logout and registration.

In the mean time, keep a lookout for the release of DrupalGap 2.x, the next generation application development kit for Drupal websites.

Comments

I got tripped up on step 0: bower; command not found. I'm not sure what to do to fix that so that I can continue the tutorial.

tyler's picture

Thanks for pointing that out, I've updated the prerequisites to include Bower, which can be installed via npm with the following command:

npm install -g bower

nice tutorial, can i know which app/site are developed with angular-drupal module.

tyler's picture

Since this module was just released, I'm guessing no apps/sites are using it yet. I personally built it as a prerequisite for DrupalGap 2.x, so we can expect much usage when DrupalGap 2.x is released: http://drupalgap.org/angular

If you do end up using the angular-drupal module, please let me know, thanks.

I like this approach to Drupal-based mobile apps. I have been learning and working on an app since Drupal Camp Ohio. I settled on AngularJS + the IONIC Framework that gets content from a Drupal 7 site with Services.

tyler's picture

Thanks for the feedback Bryan. My plan for DrupalGap 2.x (http://drupalgap.org/angular) is to use Angular JS + Bootstrap for web apps, and Angular JS + IONIC for mobile apps, while supporting Drupal 7 Services, and Drupal 8 core's REST.

I briefly read that IONIC isn't necessarily meant for web apps, and that's why I'm planning the separation between the two (plus it'll be great to support multiple front end frameworks with the development kit). Have you tried running your IONIC app as a web app in a browser?

I had a great time at DrupalCamp Ohio last year, and hope to attend/present again this year.

The IONIC app worked fine in the browser until I added Cordova features, then it broke in the browser.  Bootstrap seems like a great in-browser choice. 

Thank you for sharing your experience & knowledge. 

I've followed instructions from article and recieved error with "angular-drupal" module load. It is require to include angular-drupal.min.js file before app.js in point 3.

Thanks for your great article.

Awesome article !

Thank you.

I did run into the same problem as Andrew.

It might be worth editing your article to clarify that small point. 

angular-drupal.min.js file  needs to be before app.js

tyler's picture

Thanks, I've updated the article to reflect this.

Worked a charm!

 

hello! This is great info, thank you very much!

However im not sure how to procede now...

When i enter some credentials at the login form a cookie SESS... gets created and a log message gets added at Drupal log messages registering that  the "user session is opened". Look like the session is opened and there should be a way to retrieve the session and current user  information and that it should be persistent if i refresh the page and it should persist until the user gets logged out.
However when i refresh the page, it seems like the session gets destroyed since the cookie disapears from the browser debuger and if i try to run drupal.connect() it doesnt seem to be able to retrieve the user nor the session data.

Im doing it between two different environments, Drupal is at a server on a_domain.org

locally im using yeoman so i have the webapp running at http://localhost:9000

I installed Drupal module CORS and added this configuration

*|<mirror>|POST,ADD,GET,PUT,DELETE,OPTIONS|X-CSRF-Token|true
With this configuration is how i finnally achieved to make this hello world example work.

However now im now sure how should i build my application and handle user permissions and identify the logged in user and session information??

Im i missing something here? How should i handle the website, all of its components so once the user is logged in i can get user information and show and limit functionality according to roles and permisssions?

Thank you for this great documentation!

According to this issue (referenced above), it looks like you can now use npm to install angular-drupal.

Hi - wondering if this is still current. I went through all the steps and got this running on my linode vps. i get the login form to come up at the end but then I get an error with angular complaining about 

Uncaught Error: [$injector:nomod] Module 'angular-drupal' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.

Not sure how to troubleshoot, i double checked all my angular dependencies...

it then continues with 

 

angular.js:12783 TypeError: Cannot read property 'sitePath' of null    at new drupal (http://23.239.13.122/ctrlcon1/node_modules/angular-drupal/build/angular-...)

 

Any ideas?

Appreciate any help! Thanks

 

posted something earlier but this is just to say i got it working! turns out i had to move the angular-drupal.min.js module out of node_modules and into the app's root directory, renamed it angular-drupal.js and my issues were solved

 

thanks for an awesome idea!

angular.js:68 Uncaught Error: [$injector:nomod] Module 'angular-drupal' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.http://errors.angularjs.org/1.4.10/$injector/nomod?p0=angular-drupal

Hi Tyler,

Thanks for this tutorial.

I wanted to go a little further than login.  I modified view1 to display a node.  I have included the code to view the title and the body, for node 1.  This should work with all Drupal installations all you need to do is create a node, the Title and Body are standard fields.  Drupal will return an array of information on a field, so you need to navigate to the part of the content you want to display.  One thing not covered in the attached code is adding ngSanitize to your installation so that it will allow you to display HTML content.  Angular does not like to display HTML that has not been sanitized, Drupal does this in the Safe_Value setting but this is an easy way to pass this through Angular.  

view1.html

<div ng-controller = "View1Ctrl">

<h1>{{node.title}}</h1>

<div ng-bind-html=body></div>

 

view1.js

'use strict';

 

angular.module('myApp.view1', ['ngRoute'])

 

.config(['$routeProvider', function($routeProvider) {

  $routeProvider.when('/view1', {

    templateUrl: 'view1/view1.html',    controller: 'View1Ctrl'

  });

}])

 

.controller('View1Ctrl', function($scope, drupal) {

      drupal.node_load(1).then(function(node) {

    $scope.node = node;

    $scope.body = node.body.und[0].value;

 

});

});

Then I have a question, are views implimented?  I could not get views_json to work so I looked at the code and could not find anything for views.  Next I will see about coding this.  I should just be able to cut and past from the node_lode to add the views_json code.

 

 

 

 

 

 

tyler's picture

Although I haven't touched this in a while, Views support should be all set. See here for more information: https://github.com/easystreet3/angular-drupal/blob/7.x-1.x/README.md#views

I've got this example working working as described, but what is the benefit of replacing the theme layer with angular?

tyler's picture

Technically you can replace the theme layer with whatever you want (Foundation, roll your own CSS, etc), in this example I'd consider the theme layer as Bootstrap, and Angular as the SDK. The advantage is (IMO) that we have much tighter control over the user interfaces that we build, the big down side is we lose all Drupal theme layers (regions, blocks, form widgets, field displays, etc). The down side is the main reason I started DrupalGap.

Is the Angular-Drupal module still supported? Is there any work on supporting angular2 can you contact me about potential contract to get this module integrated into an application?

tyler's picture

The module is still supported yes, although co-maintainers are welcome. The future of this module is dependant on jDrupal, where most improvements are. I haven't personally tried Angular 2, nor have I touched Angular 1 in over a here. Thank you for the inquiry about contract work, at this time I am not taking on any new projects.

I've installed this twice, and both times it does not work properly.  I get to the final test of the app where I click the login button, or any of the buttons for that matter, nothing happens.  Any tips for troubleshooting it?  It's hosted on a Rackspace Centos server v. 6.8 using virtual hosts.  I'm fried.  Would love to get it working.  Thanks.

tyler's picture

I honestly haven't tried Angular since writing this, I've been focused soley on DrupalGap 7 and 8. I've heard some folks say that this is now required to login:

.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.withCredentials = true;
}])

Otherwise, I'd open the Console tab in my browser's development tools, and look for JavaScript warnings, and then move to the Network tab and investigate network activity to see if any errors/warning stood out.