In a recent Torque article, I gave a basic introduction to AngularJS using the WordPress REST API. I also had the privilege of presenting on this topic at WordCamp Miami and will be covering AngularJS with WordPress at an upcoming webinar for WP Engine.
This topic is important to me because I think that embracing API-driven JavaScript interfaces will help us bring the experience of WordPress forward and ensure that WordPress continues to be known for the quality of its interface.
My personal choice in a JavaScript MVC framework is Angular. You may prefer a different one, but for my work, I prefer Angular because its structure enforces a strong separation of concerns. I am a plugin developer who is stronger in PHP than JavaScript. I embraced Angular because of how simple it was to create a highly dynamic interface for my A/B testing plugin Ingot.
With Ingot, the Angular app that powers the admin is a very thin layer on top of what is still largely a PHP application that works server-side. Almost all of the code in Ingot (probably around 95 percent) is PHP. Angular is the interface that connects to the plugin via our REST API endpoints.
In this article, I’m going to discuss how to use Angular to query and update data from a server via a RESTful API. Make sure you have read my Angular basics article before proceeding, as this article assumes you are familiar with MVC basics in the context of Angular.
I will start by discussing Angular’s $http service and then later I will discuss Angular factories. For the most part, using factories to handle remote HTTP requests is better than using Angular’s $http service inside your controllers for reasons I will discuss in the second half of this article.
API First
Angular consumes JSON data from a RESTful API. In order to use it, you must have a REST API. Luckily, WordPress makes it super easy to create custom REST APIs.
I have covered how to add your own endpoints in this Torque article, in a blog post on my site, in my recent talk at WordCamp Miami, in part 2 of my REST API course, in my REST API book, and in the documentation for the REST API. Roy Sivan also wrote an article for Torque recently on how to create a custom post editor using Angular and the default routes of the WordPress REST API. You should definitely check out that article for another example of how to use AngularJS.
In my real-world use cases, custom endpoints are more useful for me since they are for my plugin. You should also note that I believe that writing a WordPress plugin designed to be used with the WordPress REST API requires a disciplined approach to your PHP code that keeps the code responsible for displaying data totally separated from the code for reading and writing the data. For me, this means having totally abstract CRUD classes and being strict about using them for updating data.
Angular HTTP
If you are a WordPress developer, you probably have experience working with jQuery AJAX. The Angular HTTP interface is almost identical to jQuery’s AJAX interface in terms of syntax and should be familiar to you. Angular is incredibly well documented and the HTTP interface is no exception. Check out the docs for the AngularJS $http service as you start experimenting with the $http interface.
In my intro to Angular article, I used hard-coded data for the models. This helped me keep the article simple and also to make it more obvious where the data in the templates was coming from. To make this useful, we will need to get data from a remote site in order to populate our models.
Let’s start with a basic example that works with two controllers: one is for a single post and one is for a list of posts. In this case, the URL is using the default WordPress post routes. The URL is for my site but you can change it to fit your needs.
In the following code, $http() takes the place of jQurery.AJAX(). If you’re familiar with jQuery AJAX, it should be readable.
ingotApp.controller( 'clickDelete', ['$scope', 'groupsFactory', function( $scope, groupsFactory ){ groupsFactory.query( { page: 1, limit: 10, context: 'admin', type: 'click' }, function ( res ) { if ( res.data.indexOf( 'No matching' ) > -1 ) { $scope.groups = {}; return; }; $scope.groups = JSON.parse( res.data ); var total_groups = parseInt( res.headers[ 'x-ingot-total' ] ); total_pages = total_groups / page_limit; $scope.total_pages = new Array( Math.round( total_pages ) ); $scope.groups.shortcode = []; }); }]);
In the first controller, we query for one single post. Angular $http implements various levels of promises. In this example, we are using the when/then syntax like in jQuery. This means that once the promise is resolved, the then() callback fires. This gives us an object with a key called data which contains the body of the request. We can use this to put the data for the post into scope with $scope.post = res.data.
In the other controller, we do a similar request but instead, we request multiple posts. In our callback, we use the data key of the response to put all of the posts into scope so we can loop through them in our view.
We are also likely to want to know how many total posts and pages of posts are available. This will allow us to provide pagination on our site. The WordPress REST API provides this information in x-wp-* headers. Angular’s $http() service returns an object for headers. As you can see in the second controller, I use these headers to get the total number of posts and pages like this:
$scope.totalPages = res.headers('x-wp-totalpages'); $scope.total = res.headers( 'x-wp-total' );
Since this code didn’t specify a transport method, the default method “GET” was used. To make changes to a post, we would need to use a POST request. Just like in jQuery, we just need to pass an argument called method to change the transport method.
In my Angular basics article, I showed a simple post editor form.
(function(angular) { 'use strict'; angular.module('learnAngular', []) .controller('postExample', ['$scope', '$http', function($scope, $http) { $http({ url: 'http://joshpress.net/wp-json/wp/v2/posts/1', cache: true } ).then( function( res ) { $scope.post = res.data; }); $scope.save = function(){ $http({ url: 'http://joshpress.net/wp-json/wp/v2/posts/1', cache: true, method: "POST" } ).then( function( res ) { $scope.post = res.data; }); }; }]).controller('postsExample', ['$scope', '$http', function($scope, $http) { $http( { url: 'http://joshpress.net/wp-json/wp/v2/posts/', cache: true } ).then( function ( res ) { $scope.posts = res.data; $scope.totalPages = res.headers('x-wp-totalpages'); $scope.total = res.headers( 'x-wp-total' ); } ); }]); })(window.angular);
That’s Just The Beginning
Now that you understand how the $http() service works in Angular, you can make fully-functional apps. It’s important to note, however, that using $http() inside of your controller isn’t the best way to work. There are two reasons for this.
The first is that it can lead to a lot of code repetition. If you need to make the same API calls in multiple places in your code, your only way to reuse the code is via cut and paste, and that’s just bad coding. Also, since the data from the API is retrieved inside of your controller, there is no way to inject mock data as part of a unit test into your controller.
Those two issues take away two of the major benefits of using Angular: Separation of concerns and testability. So, instead of using $http() inside of our controllers in Angular, we generally inject a special “factory” for querying our APIs into our controllers. These factories, in turn, use the $http() service.
Using an Angular factory as your interface for a remote API means that you have one bit of reusable code for all of your API calls. It means that if you need to change how that interface works, or even switch to a totally different API, there is only one place to change. It also means that you can mock that factory in your unit tests.
That’s all great in the abstract, but when you start writing code like the following examples, you’ll really start to love Angular.
$scope.posts = posts({ ID:7});
or
$scope.posts = posts({ tag:"star-wars"]);
If you read Roy Sivan’s article on making a post editor using AngularJS, you have seen the excellent post factory he provides in the example code for that article. In this article, I want to show you the factory that we made for Ingot.
In Ingot, the admin is based around groups. Groups have the data for a test as well as all of the variations. We use a group factory for creating, getting, and saving groups. Here is what the factory “groupsFactory” looks like:
ingotApp.factory( 'groupsFactory', function( $resource ) { return $resource( INGOT_ADMIN.api + 'groups/:id', { id: '@id', _wpnonce: INGOT_ADMIN.nonce, context: 'admin' },{ 'query' : { transformResponse: function( data, headers ) { var response = { data: data, headers: headers() }; return response; } }, 'update':{ method:'PUT', headers: { 'X-WP-Nonce': INGOT_ADMIN.nonce } }, 'post':{ method:'POST', headers: { 'X-WP-Nonce': INGOT_ADMIN.nonce } }, 'save':{ method:'POST', headers: { 'X-WP-Nonce': INGOT_ADMIN.nonce } }, 'delete':{ method:'DELETE', headers: { 'X-WP-Nonce': INGOT_ADMIN.nonce } } }) });
The syntax of this factory is very similar to what we have seen above, but it actually uses the $resource service instead of the $http service. The $http service is built on the $resource service. Using $resource allows us to make these calls return data and it also allows us to use them as functions.
When I inject groupsFactory into my controllers in Ingot, I can use groupsFactory to query the Ingot API for groups. Here is a simplified version of one of our controllers that uses the groupsFactory:
ingotApp.controller( 'clickDelete', ['$scope', 'groupsFactory', function( $scope, groupsFactory ){ groupsFactory.query( { page: 1, limit: 10, context: 'admin', type: 'click' }, function ( res ) { if ( res.data.indexOf( 'No matching' ) > -1 ) { $scope.groups = {}; return; }; $scope.groups = JSON.parse( res.data ); var total_groups = parseInt( res.headers[ 'x-ingot-total' ] ); total_pages = total_groups / page_limit; $scope.total_pages = new Array( Math.round( total_pages ) ); $scope.groups.shortcode = []; }); }]);
Now It’s Your Turn
You can take what you’ve learned in this article and apply it to use Angular and the WordPress REST API (or any API) to build better, more dynamic website front ends or to build awesome custom admin screens for WordPress.
3 Comments