Building a WordPress-Powered Front End: A Custom Directive for Post Listing

In the previous part of the series, we bootstrapped our AngularJS application, configured routing for different views, and built services around routes for posts, users, and categories. Using these services, we are now finally able to fetch data from the server to power the front end.

In this part of the series, we will be working towards building a custom AngularJS directive for the post listing feature. In the current part of the series, we will:

  • introduce ourselves to AngularJS directives and why we should create one
  • plan the directive for the post listing feature and the arguments it will take
  • create a custom AngularJS directive for post listing along with its template

So let’s start by introducing ourselves to AngularJS directives and why we need them.

Introducing AngularJS Directives

Directives in AngularJS are a way to modify the behavior of HTML elements and to reuse a repeatable chunk of code. They can be used to modify the structure of an HTML element and its children, and thus they are a perfect way to introduce custom UI widgets.

While analyzing wireframes in the first part of the series, we noted that the post listing feature is being used in three views, namely:

  1. Post listing
  2. Author profile
  3. Category posts listing

So instead of writing separate functionality to list posts on all of these three pages, we can create a custom AngularJS directive that contains business logic to retrieve posts using the services we created in the earlier part of this series. Apart from business logic, this directive will also contain the rendering logic to list posts on certain views. It’s also in this directive that the functionality for post pagination and retrieving posts on certain criteria will be defined.

Hence, creating a custom AngularJS directive for the post listing feature allows us to define the functionality only in one place, and this will make it easier for us in the future to extend or modify this functionality without having to change the code in all three instances where it’s being used.

Having said that, let’s begin coding our custom directive for the post listing feature.

Planning the Custom AngularJS Directive for Post Listing

Before we begin writing any code for building the directive for the post listing feature, let’s analyze the functionality that’s needed in the directive.

At the very basic level, we need a directive that we could use on our views for post listing, author profile, and the category page. This means that we will be creating a custom UI widget (or a DOM marker) that we place in our HTML, and AngularJS will take care of the rest depending upon what options we provide for that particular instance of the directive.

Hence, we will be creating a custom UI widget identified by the following tag:

But we also need this directive to be flexible, i.e. to take arguments as input and act accordingly. Consider the user profile page where we only want posts belonging to that specific user to show up or the category page where posts belonging to that category will be listed. These arguments can be provided in the following two ways:

  1. In the URL as parameters
  2. Directly to the directive as an attribute value

Providing arguments in the URL seems native to the API as we are already familiar with doing so. Hence a user could retrieve a set of posts belonging to a specific user in the following way:

The above functionality can be achieved by using the $routeParams service provided by AngularJS. This is where we could access parameters provided by the user in the URL. We have already looked into it while registering routes in the previous part of the series.

As for providing arguments directly to the directive as an attribute value, we could use something like the following:

The post-args attribute in the above snippet takes arguments for retrieving a specific set of posts, and currently it’s taking the author ID. This attribute can take any number of arguments for retrieving posts as supported by the /wp/v2/posts route. So if we were to retrieve a set of posts authored by a user having an ID of 1 and belonging to a category of ID 10, we could do something like the following:

The filter[cat] parameter in the above code is used to retrieve a set of posts belonging to a certain category.

Pagination is also an essential feature when working with post listing pages. The directive will handle post pagination, and this feature will be driven by the values of the X-WP-Total and X-WP-TotalPages headers as returned by the server along with the response body. Hence, the user will be able to navigate back and forth between the previous and next sets of posts.

Having decided the nitty gritty of the custom directive for post listing, we now have a fairly solid foundation to begin writing the code.

Building a Custom Directive for Post Listing

Building a directive for the post listing feature includes two steps:

  1. Create the business logic for retrieving posts and handling other stuff.
  2. Create a rendering view for these posts to show up on the page.

The business logic for our custom directive will be handled in the directive declaration. And for rendering data on the DOM, we will create a custom template for listing posts. Let’s start with the directive declaration.

Directive Declaration

Directives in AngularJS can be declared for a module with the following syntax:

Here we are declaring a directive on our module using the .directive() method that’s available in the module. The method takes the name of the directive as the first argument, and this name is closely linked with the name of the element’s tag. Since we want our HTML element to be <post-listing></post-listing>, we provide a camel-case representation of the tag name. You can learn more about this normalization process performed by AngularJS to match directive names in the official documentation.

The notation we are using in the above code for declaring our directive is called safe-style of dependency injection. And in this notation, we provide an array of dependencies as the second argument that will be needed by the directive. Currently, we haven’t defined any dependencies for our custom directive. But since we need the Posts service for retrieving posts (that we created in the previous part of the series) and the native AngularJS’s $routeParams and $location services for accessing URL parameters and the current path, we define them as follows:

These dependencies are then made available to the function which is defined as the last element of the array. This function returns an object containing directive definition. Currently, we have two properties in the directive definition object, i.e. restrict and link.

The restrict option defines the way we use directive in our code, and there can be four possible values to this option:

  1. A: For using the directive as an attribute on an existing HTML element.
  2. E: For using the directive as an element name.
  3. C: For using the directive as a class name.
  4. M: For using the directive as an HTML comment.

The restrict option can also accept any combination of the above four values.

Since we want our directive to be a new element <post-listing></post-listing>, we set the restrict option to E. If we were to define the directive using the attributes on a pre-existing HTML element, then we could have set this option to A. In that case, we could use <div post-listing></div> to define the directive in our HTML code.

The second scope property is used to modify the scope of the directive. By default, the value of the scope property is false, meaning that the scope of the directive is the same as its parent’s. When we pass it an object, an isolated scope is created for the directive and any data that needs to be passed to the directive by its parent is passed through HTML attributes. This is what we are doing in our code, and the attribute we are using is post-args, which gets normalized into postArgs.

The postArgs property in the scope object can accept any of the following three values:

  1. =: Meaning that the value passed into the attribute would be treated as an object.
  2. @: Meaning that the value passed into the attribute would be treated as a plain string.
  3. &: Meaning that the value passed into the attribute would be treated as a function.

Since we have chosen to use the = value, any value that gets passed into the post-args attribute would be treated as a JSON object, and we could use that object as an argument for retrieving posts.

The third property, link, is used to define a function that is used to manipulate the DOM and define APIs and functions that are necessary for the directive. This function is where all the logic of the directive is handled.

The link function accepts arguments for the scope object, the directive’s HTML element, and an object for attributes defined on the directive’s HTML element. Currently, we are passing two arguments $scope and $elem for the scope object and the HTML element respectively.

Let’s define some variable on the $scope property that we will be using to render the post listing feature on the DOM.

Hence we have defined six properties on the $scope object that we could access in the DOM. These properties are:

  1. $posts: An array for holding post objects that will be returned by the server.
  2. $postHeaders: An object for holding the headers that will be returned by the server along with the response body. We will use these for handling navigation.
  3. $currentPage: An integer variable holding the current page number.
  4. $previousPage: A variable holding the previous page number.
  5. $nextPage: A variable holding the next page number.
  6. $routeContext: For accessing the current path using the $location service.

The postArgs property that we defined earlier for HTML attributes will already be available on the $scope object inside the directive.

Now we are ready to make a request to the server using the Posts service for retrieving posts. But before that, we must take into account the arguments provided by the user as URL parameters as well as the parameters provided in the post-args attribute. And for that purpose, we will create a function that uses the $routeParams service to extract URL parameters and merge them with the arguments provided through the post-args attribute:

The prepareQueryArgs() method in the above code uses the angular.merge() method, which extends the $scope.postArgs object with the $routeParams object. But before merging these two objects, it first deletes the id property from the $routeParams object using the delete operator. This is necessary since we will be using this directive on category and user views, and we don’t want the category and user IDs to get falsely interpreted as the post ID.

Having prepared query arguments, we are finally ready to make a call to the server and retrieve posts, and we do so with the Posts.query() method, which takes two arguments:

  1. An object containing arguments for making the query.
  2. A callback function that executes after the query has been completed.

So we will use the prepareQueryArgs() function for preparing an object for query arguments, and in the callback function, we set the values of certain variables on the $scope property:

The callback function gets passed two arguments for the response body and the response headers. These are represented by the data and headers arguments respectively.

The headers argument is a function that returns an object containing response headers by the server.

The remaining code is pretty self-explanatory as we are setting the value of the $scope.posts array. For setting the values of the $scope.previousPage and $scope.nextPage variables, we are using the x-wp-totalpages property in the postHeaders object.

And now we are ready to render this data on the front end using a custom template for our directive.

Creating a Custom Template for the Directive

The last thing we need to do in order to make our directive work is to make a separate template for post listing and link it to the directive. For that purpose, we need to modify the directive declaration and include a templateUrl property like the following:

This templateUrl property in the above code refers to a file named directive-post-listing.html in the views directory. So create this file in the views folder and paste in the following HTML code:

This is very basic HTML code representing a single post entry and post pagination. I’ve copied it from the views/listing.html file. We will use some AngularJS directives, including ng-repeat, ng-href, ng-src, and ng-bind-html, to display the data that currently resides in the $scope property of the directive.

Modify the HTML code to the following:

The above code uses the ng-repeat directive to iterate through the $scope.posts array. Any property that is defined on the $scope object in the directive declaration is available directly in the template. Hence, we refer to the $scope.posts array directly as posts in the template.

By using the ng-repeat directive, we ensure that the container will be repeated for each post in the posts array and each post is referred to as post in the inner loop. This post object contains data in the JSON format as returned by the server, containing properties like the post title, post ID, post content, and the featured image link, which is an additional field added by the companion plugin.

In the next step, we replace values like the post title, the post link, and the featured image link with properties in the post object.

For the pagination, replace the previous code with the following:

We first access the routeContext property, which we defined in our directive declaration, and suffix it with the ?page= parameter and use the values of the nextPage and previousPage variables to navigate back and forth between posts. We also check to see if the next page or the previous page link is not null, else we add a .disabled class to the button that is provided by Zurb Foundation.

Now that we've finished the directive, it's time to test it. And we do it by placing a <post-listing></post-listing> tag in our HTML, ideally right above the <footer></footer> tag. Doing so means that a post listing will appear above the page footer. Don’t worry about the formatting and styles as we will deal with them in the next part of the series.

So that’s pretty much it for creating a custom AngularJS directive for the post listing feature.

What’s Up Next?

In the current part of the series about creating a front end with the WP REST API and AngularJS, we built a custom AngularJS directive for the post listing feature. This directive uses the Posts service that we created in the earlier part of the series. The directive also takes user input in the form of an HTML attribute and through URL parameters.

In the concluding part of the series, we will begin working on the final piece of our project, i.e. controllers for posts, users, and categories, and their respective templates.



Related Articles