1. Web Design
  2. HTML/CSS
  3. HTML

Creating Single Page Applications With WordPress and Angular.js

Scroll to top
Final product imageFinal product imageFinal product image
What You'll Be Creating

Working with the Angular.js framework is fast and rewarding, and combined with WordPress it can make a really nice SPA (Single-page Application) in a short time-span. With all the CMS controls and plugins WordPress offers, this is a interesting short-cut.

Setting Up the Theme

We will start creating a new theme by using the _tk boilerplate theme to begin with. This is a implementation of the _s underscores theme from Automattic but with Twitter’s Bootstrap implemented.

Grab the theme from GitHub, and place the files in your themes directory:

1
2
$ cd themes
3
$ wget https://github.com/Themekraft/_tk/archive/master.zip
4
wp-content/themes/$ unzip master.zip 
5
Archive:  master.zip
6
69acda231e2a2f8914374df81b89a7aa37d594fa
7
   creating: _tk-master/
8
  inflating: _tk-master/404.php      
9
  inflating: _tk-master/archive.php  
10
  inflating: _tk-master/comments.php  
11
  inflating: _tk-master/content-page.php  
12
  inflating: _tk-master/content-single.php 
13
...
14
## Rename The Directory
15
$ mv _tk-master/ angular-bootstrap
16
$ rm -f master.zip

Now that we have the _tk starter theme, we will need the npm packages angular and angular-route from inside your theme directory (we are using the name angular-bootstrap).

1
2
$ cd wp-angular
3
$ npm init
4
#follow the prompts to create your package.json file
5
... 
6
  "author": "",
7
  "license": "ISC"
8
}
9
10
11
Is this ok? (yes) yes
12
## Install The Packages
13
$ $ npm install angular angular-route --save
  • You must initialize npm within the themes directory with npm init in order to create the package.json, a file which npm uses to manage projects.
  • By using the --save flag in our npm install angular angular-route --save command we are telling npm to add the modules as dependencies to our project.
  • In the future, if we need to share this project with another developer, they will only need to run npm install in the same directory as the package.json in order to get the packages.

Now you will have the packages in your node_modules directory inside your theme. Take a look in the directory and you will be able to see several js files. We will be using angular.min.js for development

Initializing Angular

To include angular.min.js inside WordPress we need to modify the functions.php file so that we can enqueue the scripts in WordPress.

Inside functions.php, find the _tk_scripts() function and append the following to the bottom of the function:

1
2
//Load angular
3
wp_enqueue_script('angularjs', get_template_directory_uri() .'/node_modules/angular/angular.min.js');
4
wp_enqueue_script('angularjs-route', get_template_directory_uri() .'/node_modules/angular-route/angular-route.min.js');
5
wp_enqueue_script('scripts', get_stylesheet_directory_uri() . '/js/scripts.js', array( 'angularjs', 'angularjs-route' ));

You will also need to create js/scripts.js—for now just create a blank file.

Now refresh your theme in a browser, and in developer tools you will be able to see angular.min.js included now.

Using Partials

Angular.js has a great system for only updating a partial piece of HTML. To take advantage of this and the angular-route module, we will need to create a directory inside the theme named partials.

1
2
$ mkdir partials

Inside the partials directory, create a file named main.html for the purpose of testing, and add whatever HTML you like inside.

Localize the Partials Path for WordPress

For Angular to be able to load the partials, we must provide a full URL. I had some trouble using the get_stylesheet_directory_uri() method, but try it for yourself. If it does not work, use your full URL.

Add the following to the _tk_scripts function below where you added the angularjs and angular-route lines from the last step:

1
2
// With get_stylesheet_directory_uri()
3
wp_localize_script('scripts', 'localized',
4
			array(
5
				'partials' => get_stylesheet_directory_uri() . '/wp-content/themes/angular-bootstrap/partials/'
6
				)
7
	);

If this fails (which at time of writing it was for me), write in the URL, e.g.

1
2
// With hardcoded value
3
wp_localize_script('scripts', 'localized',
4
			array(
5
				'partials' => 'https://www.mydomaind.com/wp-content/themes/angular-bootstrap/partials/'
6
				)
7
	);

Enabling WP-API

For Angular to work with WordPress, we need to enable the WP-API REST Plugin. This is simple, as it is just the installation of a plugin.

Download and install the plugin from git, and run the following in your plugins dir:

1
2
git clone git@github.com:WP-API/WP-API.git json-rest-api

Then enable the plugin in your wp-admin panel. You will be able to see JSON output at your-wordpress.com/wp-json once it is enabled.

Building Routes

Routes make up the specific pages of your blog. We can define one for our main.html partial now—and configure it to be shown at the index page of our WordPress.

First ensure the Angular.js app is defined via the ng-app attribute, and in header.php make the following:

1
2
<!DOCTYPE html>
3
4
<html <?php language_attributes(); ?> ng-app="wp">
5
<head>
6
<base href="/">

Here we are calling the app wp with the ng-app attribute. Also we set the base tag so that Angular can find the JSON we have enabled in WP-API.

Add the following to js/scripts.js:

1
2
angular.module('wp', ['ngRoute'])
3
.config(function($routeProvider, $locationProvider) {
4
	$routeProvider
5
	.when('/', {
6
		templateUrl: localized.partials + 'main.html',
7
		controller: 'Main'
8
	})
9
})
10
.controller('Main', function($scope, $http, $routeParams) {
11
	$http.get('wp-json/posts/').success(function(res){
12
		$scope.posts = res;
13
	});
14
});

Now inside partials/main.html add this:

1
2
<ul>
3
    <li ng-repeat="post in posts">
4
		<a href="{{post.slug}}">
5
			{{post.title}}
6
		</a>
7
	</li>
8
</ul>

And finally inside index.php, directly after get_header.php(), add the Angular attribute ng-view on a div tag.

1
2

Refresh the index of your WordPress, and a bullet list of your blog posts will now be displayed on the home page.

This is due to the ng-controller invoking the Main controller from scripts.js and the ng-view attribute specifying where Angular should render.

Displaying a Post By Slug

Let’s add the route now for displaying a WordPress blog via the URL slug.

Open js/scripts.js and adjust the file so it reads as follows:

1
2
angular.module('wp', ['ngRoute'])
3
.config(function($routeProvider, $locationProvider) {
4
5
	$routeProvider
6
	.when('/', {
7
		templateUrl: localized.partials + 'main.html',
8
		controller: 'Main'
9
	})
10
	.when('/:slug', {
11
		templateUrl: localized.partials + 'content.html',
12
		controller: 'Content'
13
	})
14
	.otherwise({
15
		redirectTo: '/'
16
	});
17
})
18
.controller('Main', function($scope, $http, $routeParams) {
19
	$http.get('wp-json/posts/').success(function(res){
20
		$scope.posts = res;
21
	});
22
})
23
.controller('Content',
24
		['$scope', '$http', '$routeParams', function($scope, $http, $routeParams) {
25
			$http.get('wp-json/posts/?filter[name]=' + $routeParams.slug).success(function(res){
26
				$scope.post = res[0];
27
			});
28
		}
29
	]
30
);

By adding the Content controller, we can specify the $http.get URI for the JSON posts, and specify the slug as the filter parameter.

To create this we use the following code: $http.get('wp-json/posts/?filter[name]=' + $routeParams.slug).

Note: In order to get the /:slug route working, you must specify /%postname%/ as your permalink structure in the wp-admin.

Make sure to set the content.html with the following:

1
2
{{post.title}}
3
{{post.content}}

Now if you refresh the page, you will be able to navigate to your blog posts via the links in the bullet list you made in the previous step.

Using Angular Services in WordPress

So far we have seen how to create routes and start working with the wp-json API. Before we start to write any logic we need a place for it to go, and that is within a Angular service (in this example we use a Factory service).

Create a new file js/services.js and add the following code to retrieve categories and posts:

1
2
3
function ThemeService($http) {
4
5
    var ThemeService = {
6
		categories: [],
7
		posts: [],
8
		pageTitle: 'Latest Posts:',
9
		currentPage: 1,
10
		totalPages: 1,
11
		currentUser: {}
12
	};
13
14
	//Set the page title in the <title> tag
15
	function _setTitle(documentTitle, pageTitle) {
16
		document.querySelector('title').innerHTML = documentTitle + ' | AngularJS Demo Theme';
17
		ThemeService.pageTitle = pageTitle;
18
	}
19
20
	//Setup pagination
21
	function _setArchivePage(posts, page, headers) {
22
		ThemeService.posts = posts;
23
		ThemeService.currentPage = page;
24
		ThemeService.totalPages = headers('X-WP-TotalPages');
25
	}
26
27
	ThemeService.getAllCategories = function() {
28
    	//If they are already set, don't need to get them again
29
		if (ThemeService.categories.length) {
30
			return;
31
		}
32
33
		//Get the category terms from wp-json
34
		return $http.get('wp-json/taxonomies/category/terms').success(function(res){
35
			ThemeService.categories = res;
36
		});
37
	};
38
39
	ThemeService.getPosts = function(page) {
40
		return $http.get('wp-json/posts/?page=' + page + '&filter[posts_per_page]=1').success(function(res, status, headers){
41
        	ThemeService.posts = res;
42
			page = parseInt(page);
43
44
			// Check page variable for sanity
45
			if ( isNaN(page) || page > headers('X-WP-TotalPages') ) {
46
				_setTitle('Page Not Found', 'Page Not Found');
47
			} else {
48
				//Deal with pagination
49
				if (page>1) {
50
					_setTitle('Posts on Page ' + page, 'Posts on Page ' + page + ':');
51
				} else {
52
					_setTitle('Home', 'Latest Posts:');
53
				}
54
55
				_setArchivePage(res,page,headers);
56
			}
57
		});
58
	};
59
60
	return ThemeService;
61
}
62
63
//Finally register the service
64
app.factory('ThemeService', ['$http', ThemeService]);
65

This is a basic factory setup, where we have two internal functions _setTitle and _setArchivePage. These methods are called from getPosts and getCategories to update the current page title and also set an internal integer to know which page number we are looking at.

We will need to begin using the ngSanitize module for parsing inputs to our service. Install this with npm as so inside your theme directory:

1
2
$ npm install angular-sanitize --save

The ThemeService is just a basic JavaScript Object which performs a category lookup via $http.get, as is the getPosts method. We will now make our controller aware of this service. Open scripts.js and modify the controller to be aware of ThemeService.

1
2
//Main controller
3
app.controller('Main', ['$scope', '$http', 'ThemeService', function($scope, $http, ThemeService) {
4
	//Get Categories from ThemeService
5
	ThemeService.getAllCategories();
6
	
7
	//Get the first page of posts from ThemeService
8
	ThemeService.getPosts(1);
9
10
	$scope.data = ThemeService;
11
}]);

Don’t forget to enable the angular-sanitize module inside your scripts.js also on the first line with:

1
2
var app = angular.module('wp', ['ngRoute', 'ngSanitize']);

Finally you will need to ensure the js/services.js is enqueued into WordPress as well as the angular-sanitize module. Do so by modifying the functions.php file and appending the following before the wp_localize_script call:

1
2
wp_enqueue_script('angularjs-sanitize', get_stylesheet_directory_uri() . '/node_modules/angular-sanitize/angular-sanitize.min.js');
3
wp_enqueue_script('theme-service', get_stylesheet_directory_uri() . '/js/services.js');

Now we will need to update the main.html partial to display these categories that are being provided by the ThemeService.

1
2
<h1>Categories</h1>
3
<ul>
4
    <li ng-repeat="category in data.categories">
5
		<span ng-if="current_category_id && category.ID == current_category_id" ng-bind-html="category.name"></span>
6
		<a ng-if="!current_category_id || category.ID != current_category_id" href="category/{{category.slug}}" ng-bind-html="category.name"></a>
7
	</li>
8
</ul>
9
10
<p>{{data.pageTitle}}</p>
11
<ul>
12
	<li ng-repeat="post in data.posts">
13
		<a href="/{{post.slug}}" ng-bind-html="post.title"></a>
14
		<a href="/{{post.slug}}" ng-if="post.featured_image_thumbnail_url"><img ng-src="{{post.featured_image_thumbnail_url}}" alt="{{post.featured_image.title.rendered}}" /></a>
15
		<div ng-bind-html="post.excerpt.rendered"></div>
16
	</li>
17
</ul>

You will now be able to see your posts and categories displayed on your home page via ng-view using a factory service for Angular. The benefit of this is that all components will have the data available to them. So we can now share the categories object between all of our controllers in our routes.

Taking Things Further

Now that we have a service set up for our theme, we can continue developing the data layer and incorporate Bootstrap styling into the returned HTML.

The possibilities now that Angular is configured in your theme are truly endless, and by checking out the repository provided, you will have a quick starting point for creating Angular- and Bootstrap-enabled single-page WordPress applications.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.