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:
$ cd themes $ wget https://github.com/Themekraft/_tk/archive/master.zip wp-content/themes/$ unzip master.zip Archive: master.zip 69acda231e2a2f8914374df81b89a7aa37d594fa creating: _tk-master/ inflating: _tk-master/404.php inflating: _tk-master/archive.php inflating: _tk-master/comments.php inflating: _tk-master/content-page.php inflating: _tk-master/content-single.php ... ## Rename The Directory $ mv _tk-master/ angular-bootstrap $ 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
).
$ cd wp-angular $ npm init #follow the prompts to create your package.json file ... "author": "", "license": "ISC" } Is this ok? (yes) yes ## Install The Packages $ $ npm install angular angular-route --save
- You must initialize
npm
within the themes directory withnpm init
in order to create thepackage.json
, a file whichnpm
uses to manage projects. - By using the
--save
flag in ournpm install angular angular-route --save
command we are tellingnpm
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 thepackage.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:
//Load angular wp_enqueue_script('angularjs', get_template_directory_uri() .'/node_modules/angular/angular.min.js'); wp_enqueue_script('angularjs-route', get_template_directory_uri() .'/node_modules/angular-route/angular-route.min.js'); 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
.
$ 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:
// With get_stylesheet_directory_uri() wp_localize_script('scripts', 'localized', array( 'partials' => get_stylesheet_directory_uri() . '/wp-content/themes/angular-bootstrap/partials/' ) );
If this fails (which at time of writing it was for me), write in the URL, e.g.
// With hardcoded value wp_localize_script('scripts', 'localized', array( 'partials' => 'http://www.mydomaind.com/wp-content/themes/angular-bootstrap/partials/' ) );
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:
git clone [email protected]: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:
<!DOCTYPE html> <html <?php language_attributes(); ?> ng-app="wp"> <head> <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
:
angular.module('wp', ['ngRoute']) .config(function($routeProvider, $locationProvider) { $routeProvider .when('/', { templateUrl: localized.partials + 'main.html', controller: 'Main' }) }) .controller('Main', function($scope, $http, $routeParams) { $http.get('wp-json/posts/').success(function(res){ $scope.posts = res; }); });
Now inside partials/main.html
add this:
<ul> <li ng-repeat="post in posts"> <a href="{{post.slug}}"> {{post.title}} </a> </li> </ul>
And finally inside index.php
, directly after get_header.php()
, add the Angular attribute ng-view
on a div
tag.
<div ng-view></div>
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:
angular.module('wp', ['ngRoute']) .config(function($routeProvider, $locationProvider) { $routeProvider .when('/', { templateUrl: localized.partials + 'main.html', controller: 'Main' }) .when('/:slug', { templateUrl: localized.partials + 'content.html', controller: 'Content' }) .otherwise({ redirectTo: '/' }); }) .controller('Main', function($scope, $http, $routeParams) { $http.get('wp-json/posts/').success(function(res){ $scope.posts = res; }); }) .controller('Content', ['$scope', '$http', '$routeParams', function($scope, $http, $routeParams) { $http.get('wp-json/posts/?filter[name]=' + $routeParams.slug).success(function(res){ $scope.post = res[0]; }); } ] );
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:
<h1>{{post.title}}</h1> {{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:
function ThemeService($http) { var ThemeService = { categories: [], posts: [], pageTitle: 'Latest Posts:', currentPage: 1, totalPages: 1, currentUser: {} }; //Set the page title in the <title> tag function _setTitle(documentTitle, pageTitle) { document.querySelector('title').innerHTML = documentTitle + ' | AngularJS Demo Theme'; ThemeService.pageTitle = pageTitle; } //Setup pagination function _setArchivePage(posts, page, headers) { ThemeService.posts = posts; ThemeService.currentPage = page; ThemeService.totalPages = headers('X-WP-TotalPages'); } ThemeService.getAllCategories = function() { //If they are already set, don't need to get them again if (ThemeService.categories.length) { return; } //Get the category terms from wp-json return $http.get('wp-json/taxonomies/category/terms').success(function(res){ ThemeService.categories = res; }); }; ThemeService.getPosts = function(page) { return $http.get('wp-json/posts/?page=' + page + '&filter[posts_per_page]=1').success(function(res, status, headers){ ThemeService.posts = res; page = parseInt(page); // Check page variable for sanity if ( isNaN(page) || page > headers('X-WP-TotalPages') ) { _setTitle('Page Not Found', 'Page Not Found'); } else { //Deal with pagination if (page>1) { _setTitle('Posts on Page ' + page, 'Posts on Page ' + page + ':'); } else { _setTitle('Home', 'Latest Posts:'); } _setArchivePage(res,page,headers); } }); }; return ThemeService; } //Finally register the service app.factory('ThemeService', ['$http', ThemeService]);
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:
$ 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
.
//Main controller app.controller('Main', ['$scope', '$http', 'ThemeService', function($scope, $http, ThemeService) { //Get Categories from ThemeService ThemeService.getAllCategories(); //Get the first page of posts from ThemeService ThemeService.getPosts(1); $scope.data = ThemeService; }]);
Don’t forget to enable the angular-sanitize
module inside your scripts.js
also on the first line with:
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:
wp_enqueue_script('angularjs-sanitize', get_stylesheet_directory_uri() . '/node_modules/angular-sanitize/angular-sanitize.min.js'); 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.
<h1>Categories</h1> <ul> <li ng-repeat="category in data.categories"> <span ng-if="current_category_id && category.ID == current_category_id" ng-bind-html="category.name"></span> <a ng-if="!current_category_id || category.ID != current_category_id" href="category/{{category.slug}}" ng-bind-html="category.name"></a> </li> </ul> <p>{{data.pageTitle}}</p> <ul> <li ng-repeat="post in data.posts"> <a href="/{{post.slug}}" ng-bind-html="post.title"></a> <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> <div ng-bind-html="post.excerpt.rendered"></div> </li> </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.
Comments