cmcintosh's picture

Getting Content From Drupal 8 with Angular.js

It is probably one of the easiest things to accomplish in the Headless Drupal 8 world, getting Nodes, Users, Taxonomy terms or other content can quickly be done by simplely enabling the Views module and RESTful modules that now are a part of Drupal 8 core.  The great thing about this is the fact that you can be up and running with in a few minutes.  

Create a RESTful View

So once you enable those to modules you will want to create a new view with a Rest Export display.  You can create this for any entity that comes with view support.  For our purpose I am just going to stick to nodes. 

Once you create the new view, under Format set Show to Fields instead of Entity.  This will allow you to minimize the amount of data that is being transmitted when you make a call to your headless drupal 8 website.  For this example I will be just sending the Title, Author, Summary, and body of the node.  This will be enough for my Angular app to render some blog posts that I want to showcase.  Once you add the fields you want, you can click on the field Settings under format and assign easy to read aliases for your fields.  You will also want to make sure that Drupal is not outputting any of the fields as links.  If you are sending a image, you will probably wany to setup the view to render it using the URL as the output.  This can also be useful if you want to have your headless Drupal 8 site handle resizing images for various platforms.

Once you are done with that the next step is to actually have a way that your Angular.js Application can consume content from your Headless Drupal 8 site, for that I have found creating a simple Factory to be the easiest method to make requests from Drupal and then render them in controllers.  Below is an example of that in practice.  Some of the important things about this is the fact that you will want to make sure that you have CORS configured if your angular application is on another domain from your Headless Drupal 8 site.  I talked about doing that in my Introduction to Headless Drupal 8 blog post

app.factory('Node', ['$http', '$q', nodeService]);

/**
* Define a service used to retrieve Node data from the server.
*/
function nodeService($http, $q) {
  var Service = {
    index: nodeIndex,
    get: nodeGet,
    save: nodeSave
  };
  var url = "/api/v1/node";

  /**
  * Return the index of nodes.
  */
  function nodeIndex() {
    var def = $q.defer();

      $http.get(url)
      .success( function(data){
        def.resolve(data);
      })
      .error( function(error){
        def.reject(error);
      });

    return def.promise;
  }

  return Service;
}

 

Things to remember will be to use the Defer functionality when running the http request to your headless Drupal 8 site so that you do not hold up the rest of the application.  Once you have your service created you can then pass it into a controller and call it like this:

 

app.controller('blogRiver', ['$scope', 'Node', blogRiverController]);

/**
* Controller for the River view for the Blog.
*/
function blogRiverController($scope, Node) {
  $scope.blogs = [];

  Node.index().then(
    function (data) {
      $scope.blogs = data;
    },
    function (error) {
      
      
    }
  );
}

 

Submitting Forms with Angular.js to a Headless Drupal 8 Site

Submitting form data can be a bit tricky with a Headless Drupal 8 site.  I have found that is is easiest to create a custom end-point to handle incoming submissions from an Angular application, and call the correct Headless Drupal 8 form, using Drupal 8's new form api system.  This will allow you to get information back regarding the validation of the submitted data and notify the user of the status of the form submission information.  You will need to create a Drupal 8 module to help with this.  While I will not being going too indepth with creating custom Drupal 8 modules, I will outline what you need in order to make this work.

Headless Drupal 8 Menu Routing file: demo.routing.yml 

 

demo.user_register:
  path: '/api/v1/user/register'
  defaults:
    _controller: '\Drupal\demo\Controller\RemoteUser::register'
  requirements:
    _permission: 'access content'

 

This is a very simple Yaml file that will define the endpoint your application will post data to in order to get a response from Drupal.  For this example I am going to use the Drupal 8's user registration form in order to demonstrate both handling form data with headless drupal 8, but also user creation on a headless drupal 8 site.  The next thing you will need to do is create the Controller file that will contain the handler for this callback url.

Headless Drupal 8 Form Callback Class: src\Controller\User.php

 

<?php

/**
 * @file
 * Contains \Drupal\demo\Controller\RemoteUser
 */

 namespace Drupal\demo\Controller;

 use Drupal\Core\Controller\ControllerBase;
 use Drupal\user\Controller;
 use Symfony\Component\HttpFoundation\JsonResponse;

 class RemoteUser extends ControllerBase {

   public function register() {
     $form_state = (new FormState())->setValues($_POST);
     \Drupal::formBuilder()->submitForm('\Drupal\user\RegisterForm', $form_state);

     // Check for errors from the from
     if ($errors = $form_state->getErrors()) {
        // Return errors to notify the client.
        return new JsonResponse( array( 'error' => $errors ));
     }
     else {
       // Return new user session to client.
       $uid = \Drupal::service('user.auth')->authenticate($_POST['name'], $_POST['pass']);
       return new JsonResponse( array( 'uid' => $uid, 'name' => $_POST['name'] ) );
     }

   }
 }

 

As you can see from above I am taking the posted data from the angular application, and feeding it to the User Registration form.  I then check for any errors returned from the form, and if there are any I stop and send the information back to our client.  However, if there are no issues with the form, I then continue and authenticate the user and return that information to the client to be used however the client wishes.

Angular.js Service for posting forms to Drupal.  Because we are wanting to do a $http post we will need to handle those things slightly different from a typical GET request.

 

function registerUser(email, username, password) {
    var defer = $q.defer();
    var data = {
      'username' : username,
      'mail' : email,
      'pass' : password
    };

    var config = {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
      }
    };

    $http.post('/api/v1/user/register', data, config)
      .success(function(data, status, headers, config){
        defer.resolve({
          'data': data,
          'status' : status,
          'headers': headers,
          'config': config,
        });
      })
      .error(function(data,status,header,config){
        defer.reject({
          'data': data,
          'status' : status,
          'headers': headers,
          'config': config,
        });

        Service.data.uid = data.uid;
        Service.data.name = data.name;
      });

    return defer.promise;
  }

 

You will need to make sure to set your headers so that you can post to your headless Drupal 8 site.  In addition you will want to some how handle errors returned or user information.  I wont directly discuss this now, but will show some examples of things you can do with Sessions below.  Other than that it is a standard setup in order to create a function in a Factory that will return the result when a user registers via your Angular application.

Registering, Logging in, and handling Sessions with Angular and a Headless Drupal 8 site

Now that your able to handle posting form information to your headless drupal 8 site as well as getting content from your headless drupal 8, the next step is to control who has access to do what on your application.  There are two steps i think can be used in order to do this.  To this end I will be using Drupal to create, validate, and destroy sessions.  Then I will be using a custom end point to help with validating permissions in my Angular Application.

Starting a Headless Drupal 8 Session:

 

/**
   * Takes post information in order to authenticate remote sessions.
 */
   public function start() {
     \Drupal::logger('demo')->notice(print_r($_POST, true));
     $uid = \Drupal::service('user.auth')->authenticate($_POST['name'], $_POST['pass']);
     $session_manager = Drupal::service('session_manager');
     $session_id = $session_manager->getId();
     return new JsonResponse( array( 'uid' => $uid, 'name' => $_POST['name'], 'session_id' => $session_id ) );
   }

 

I take the user posted Name and password from the Angular application, and then I pass it to the user.auth service.  Once I have logged in I call the Session manager service in order to get the correct session id.  I pass that, the name, and user id back to the angular application.  I will be using this to communicate with Drupal whenever we need to do a permission check or when we submit future data in the forms (this is especially useful when building a Headless Commerce application with angular).

Since Angular is javascript based and you could lose your session if you reloaded the page, then it maybe a wise idea to set a user cookie with the returned data, something like:

 

$cookies.put('uid', response.data.uid);
$cookies.put('name', response.data.name);
$cookies.put('session_id', response.data.session_id);

 

This would allow your application to reinitialize the session if for some reason the user had to reload your application or if the user is returning later on in a new visit to the site.  I still have a lot more planned that I want to write on this topic, but for now I think I will leave it here.  If you found this article interesting, useful, or have questions feel free to post your comments below and I will respond as quickly as I can.

Comments

thank you for this tutorial, i've been searching for remote user login/registration
i think you frogot to use "Drupal\Core\Form\FormState" in RemoteUser controller.

cmcintosh's picture

I believe your right.  This is a bit of an older tutorial, and the principles hold.  I am thinking of doing a updated version of this here soon with some added things that I have learned over the last week or so.

Add new comment

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-9590928518252466"
data-ad-slot="3345084432">

Error | Wembassy

Error

The website encountered an unexpected error. Please try again later.