Angular 2 Boilerplate Setup for Small Drupal 7 Applications

If you've ever wanted to use Angular for part of a Drupal project, you probably know that a quick google search is going to return all kinds of results about using Angular as a frontend for a detached, headless Drupal backend. But that's not always what you want. You might have an existing site that could be improved by a small scale Angular based component. Or maybe you're wary of the SEO issues that could arise from having an SPA.

Thankfully, this isn't a total no-man's-land. There's an excellent blog post on how to use Angular to provide a drupal component over at Sitepoint. But it's for the first version of Angular, which is unsurprising since it was was written in 2014. It's also trying to do two things at once - showing how to use Angular and providing a marginally useful output.

I'm going to show how to set up an Angular 2 boilerplate module. It doesn't do anything fancy, of course - what your project actually calls for is up to you. But this will get you over that first hurdle and let you start working.

The Drupal Module

Start by creating an empty module. This should be familiar territory. Again, this is going to be extremely bare-bones for now - just a menu item and an empty callback.

angular_demo.info

name = Angular Boilerplate Demo
description = Angular JS Demo
core = 7.x

angular_demo.module

function angular_demo_menu() {
  $items = [];
  $items['angular2'] = [
    'access callback' => TRUE,
    'page callback' => 'angular_demo_callback',
  ];
  return $items;
}

function angular_demo_callback() {
}

 

In order to keep this compact and reusable, we're going to use a theme function to define the template for this. Implement hook_theme in angular_demo.module so we can provide a template within the module.

angular_demo.module (addition)

function angular_demo_theme($existing, $type, $theme, $path) {
  return [
    'angular_component' => [
      'template' => 'angular-component',
      'variables' => array(),
      'path' => drupal_get_path('module', 'angular_demo') . '/theme',
    ],
  ];
}

Now, let's create a directory called theme inside our module, and inside of it create our template. We'll just use some basic HTML so we can smoke-test it.

theme/angular-component.tpl.php

<h1>Test</h1>

This is almost enough to let us confirm that our module is working. Let's populate the page callback.

angular_demo.module (modification)

function angular_demo_callback() {
  $build['content'] = [
    '#theme' => [
      'angular_component',
    ],
  ];
  return $build;
}

Enable your module and point your browser at /angular2 on your development environment. You should see the word Test displayed as an h1, inside your site's existing page structure, fully themed.

Adding Angular 2

We do need something for Angular 2 to do, and in that regard the Angular 2 Quickstart provides us the code we need for a Hello World. Note that I've linked to the JS quickstart - compiling JS from Typescript is outside the scope of this tutorial.

We'll start by actually getting the Angular code files. You could put them inside this module, but I'm going to put them in the libraries directory with the assumption that I might need to create another component later.

The quickstart gives us a package.json file. I'm going to put this in sites/all/libraries so that when I run npm-install, I'll have a node_modules directory in that location.

sites/all/libraries/package.json

{
  "dependencies": {
    "@angular/common": "~2.1.0",
    "@angular/compiler": "~2.1.0",
    "@angular/core": "~2.1.0",
    "@angular/forms": "~2.1.0",
    "@angular/http": "~2.1.0",
    "@angular/platform-browser": "~2.1.0",
    "@angular/platform-browser-dynamic": "~2.1.0",
    "@angular/router": "~3.1.0",
    "@angular/upgrade": "~2.1.0",
    "angular-in-memory-web-api": "~0.1.5",
    "bootstrap": "^3.3.7",
    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.8",
    "rxjs": "5.0.0-beta.12",
    "zone.js": "^0.6.25"
  },
  "devDependencies": {
    "concurrently": "^3.0.0",
    "lite-server": "^2.2.2"
  }
}

Now, I'm all but certain we don't need lite-server, and possibly concurrently, but I'm leaving them in to keep parity with the quickstart. Once you've created this file, go do sites/all/libraries on the command line and run npm-install. When it's done you should see a tree of installed components.

Now we need to add the JS files to our render element, using #attached.

angular_demo.module (modification)

function angular_demo_callback() {   $build['content'] = [
    '#theme' => [
      'angular_component',
    ],
    '#attached' => [
      'js' => [
        libraries_get_path('node_modules') . '/core-js/client/shim.min.js',
        libraries_get_path('node_modules') . '/zone.js/dist/zone.js',
        libraries_get_path('node_modules') . '/reflect-metadata/Reflect.js',
        libraries_get_path('node_modules') . '/rxjs/bundles/Rx.js',
        libraries_get_path('node_modules') . '/@angular/core/bundles/core.umd.js',
        libraries_get_path('node_modules') . '/@angular/common/bundles/common.umd.js',
        libraries_get_path('node_modules') . '/@angular/compiler/bundles/compiler.umd.js',
        libraries_get_path('node_modules') . '/@angular/platform-browser/bundles/platform-browser.umd.js',
        libraries_get_path('node_modules') . '/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      ],
    ],
  ];
  return $build; 
}

Create the JS files from the quickstart. I'm just going to put these in the module root for now - best practices for naming and location of these files is up to you.

angular_demo.component.js

(function(app) {
  app.AppComponent =
    ng.core.Component({
      selector: 'my-app',
      template: '<h1>My First Angular App</h1>'
    })
    .Class({
      constructor: function() {}
    });
})(window.app || (window.app = {}));

angular_demo.module.js

(function(app) {
  app.AppModule =
    ng.core.NgModule({
      imports: [ ng.platformBrowser.BrowserModule ],
      declarations: [ app.AppComponent ],
      bootstrap: [ app.AppComponent ]
    })
    .Class({
      constructor: function() {}
    });
})(window.app || (window.app = {}));

angular_demo.main.js

(function(app) {
  document.addEventListener('DOMContentLoaded', function() {
    ng.platformBrowserDynamic
      .platformBrowserDynamic()
      .bootstrapModule(app.AppModule);
  });
})(window.app || (window.app = {}));

And now we add these files to our #attached array.angular_demo.module (modification)

function angular_demo_callback() {
  $build = [];

  $build['content'] = [
    '#theme' => [
      'angular_component',
    ],
    '#attached' => [
      'js' => [
        libraries_get_path('node_modules') . '/core-js/client/shim.min.js',
        libraries_get_path('node_modules') . '/zone.js/dist/zone.js',
        libraries_get_path('node_modules') . '/reflect-metadata/Reflect.js',
        libraries_get_path('node_modules') . '/rxjs/bundles/Rx.js',
        libraries_get_path('node_modules') . '/@angular/core/bundles/core.umd.js',
        libraries_get_path('node_modules') . '/@angular/common/bundles/common.umd.js',
        libraries_get_path('node_modules') . '/@angular/compiler/bundles/compiler.umd.js',
        libraries_get_path('node_modules') . '/@angular/platform-browser/bundles/platform-browser.umd.js',
        libraries_get_path('node_modules') . '/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
        drupal_get_path('module', 'angular_demo') . '/angular_demo.component.js',
        drupal_get_path('module', 'angular_demo') . '/angular_demo.module.js',
        drupal_get_path('module', 'angular_demo') . '/angular_demo.main.js',
      ],
    ],
  ];

  return $build;
}

From here, the last step is to change our template file. If you look at the index.html in the quickstart guide, you'll see the expected markup is a single tag inside the body.

themes/angular-component.tpl.php (modification)

<my-app>Loading...</my-app>

 

Reload your page, and you should see an H1 that says "My First Angular App". If it just says "Loading..." then you've gone wrong somewhere - double check that all your JS files are being included.

If you're still having trouble, or just want to save yourself the copy/pasting efforts, you can find a working sample at the Alloy Github. Don't forget to copy package.json.dist to sites/all/libraries/package.json and run npm-install. It doesn't matter if you do it before or after enabling the module, but it does have to be done for it to work.

Drupal

Get Help With Your Drupal Project

At Alloy, we're experts at Drupal projects large and small. Contact us today for help with your Drupal Project.