Angular 2 WP Theme - Routing to Individual Articles

October 2, 2016 by Stuart Kuentzel

This is the second part in a series. This post won’t make a heck of a lot of sense if you haven’t read the first, which you can read at Angular 2 WP Theme Setup. The full source code can be viewed on GitHub.

Updated for final version of Angular 2

In the first part of this tutorial, we set up Angular 2 with the WP API 2. (Update: Plugin no longer necessary if you’re using anything above WP 4.7. API is now core.) We pieced together a service that made a call to the API, and a component that rendered the data to our view. In this tutorial, we’ll add functionality so when we click on a post from the list of posts, the page changes and loads the specific post data from the WP API.

We’ll be using the Angular CLI to do a lot of the work for us, so make sure you have that installed. We already have routing in place, so this should be a breeze :)

Create a Single Post Component

Open up the project in the text editor of your choice, and take a look at /src/app/posts. In the previous tutorial, we created a component to list out posts from WordPress. Let’s use the CLI to create another component that handles rendering a single post.

In the terminal, navigate to the root of your project. Then run ng g component /posts/post-single. Inside of the posts directory, we can find a new folder called post-single where our new component lives.

Routing For a Single Post

In the last tutorial, we set up routing, so that PostListComponent would take control of the app on the homepage. Let’s create a new route for single posts. Open up src/app/app-routing.module.ts

// app-routing.module.ts

...

import { PostSingleComponent } from './posts/post-single/post-single.component';

const routes: Routes = [
  {
    path: '',
    component: PostListComponent,
    pathMatch: 'full'
  },
  {
    path: ':slug',
    component: PostSingleComponent
  }
];

...

In our app router, we’re importing the new component, PostSingleComponent, and assigning it to a route. Now, when there is a slug in the URL, PostSingleComponent will take charge.

ng2 Click Event

Ok, the route is set up. Now, when we click a post from the list, we need the PostListComponent to handle the click event. We’ll write a function inside of the component to be called when a post is clicked in the template. First, let’s create a function in post-list.component to navigate to the single post. We’ll need to import Router into our PostListComponent.

post-list.component

import { Component, OnInit } from '@angular/core';
import { Post } from '../post';
import { PostsService } from '../posts.service';
import { Router } from '@angular/router';

Next, we’ll create an instance of the router to use when PostListComponent is active. We do this inside of the constructor…

// post-list.component.ts

constructor( private postsService: PostsService, private router: Router ) { }

Finally, we’ll write the function to be fired when a single post is selected…

// post-list.component.ts

...

ngOnInit() {
	this.getPosts();
}

selectPost(slug) {
	this.router.navigate([slug]);
}

...

We added the selectPost function below ngOnInit, but it could be above it as well. We’re using the instance of router that we just created in the constructor, and passing a slug to the navigate method.

All we have left to do is attach the click event to our list of posts. We’ll do that inside of the post-list template…

/* post-list.component.html */

<ul>
  <li *ngFor="let post of posts" (click)="selectPost(post.slug)">
    {{ post.title.rendered }}
  </li>
</ul>

In ng2, this is how we attach a listener to an element. Inside of our PostListComponent, whenever we click one of the posts, it’s going to fire the selectPost function we just wrote.

In your command line, run ng serve. Now, when you click on a post, the app will navigate you to a new page and say “post-single works!”, which means the PostSingleComponent is taking over. Also, The url also matches the name of the single post. Good stuff. We’re halfway there.

Adjusting PostsService

We’ll have to create a new function in the PostsService to fetch a specific post by slug.

// posts.service.ts

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Post } from './post';

@Injectable()
export class PostsService {

  private _wpBase = "http://{YOUR-SITE-HERE}.com/wp-json/wp/v2/";

  constructor(private http: Http) { }

  getPosts(): Observable<Post[]> {

      return this.http
        .get(this._wpBase + 'posts')
        .map((res: Response) => res.json());

  }

  getPost(slug): Observable<Post> {

      return this.http
        .get(this._wpBase + `posts?slug=${slug}`)
        .map((res: Response) => res.json());

  }

}

The getPost (singular) function is a lot like the getPosts (plural) function, but we’re returning more specific data from WP API 2. You can see in the .get method, we filter by name and search by slug, which we are passing from the PostSingleComponent.

Pulling the Slug From the Activated Route

If we’re going to use a slug in PostsService to fetch data from the WP API, we’ll need the PostSingleComponent to pass that slug to the service when it’s initialized. But the PostSingleComponent doesn’t explicitly know the slug yet. In order to remedy this, we’re going to have to make some changes to post-single.component. As always, we’ll start by importing what we need…

// post-single.component.ts

import { Component, OnInit } from '@angular/core';
import { Post } from '../post';
import { PostsService } from '../posts.service';
import { Router, ActivatedRoute, Params } from '@angular/router';

@Component({
  selector: 'app-post-single',
  templateUrl: './post-single.component.html',
  styleUrls: ['./post-single.component.css'],
  providers: [PostsService]
})

Note that we’re setting PostsService as the provider of this components data.

// post-single.component.ts

export class PostSingleComponent implements OnInit {

  post: Post;

  constructor( private postsService: PostsService, private route: ActivatedRoute ) { }

  getPost(slug){
    this.postsService
      .getPost(slug)
      .subscribe(res => {
        this.post = res[0];
      });
  }

  ngOnInit() {

    this.route.params.forEach((params: Params) => {
       let slug = params['slug'];
       this.getPost(slug)
    });

  }

}

Let’s break this down piece by piece. First, we create a post variable, and set it to expect an instance of the Post class we created.

Below, inside of our constructor, we are creating some instances to use locally within our component. We’ll use PostsService and ActivatedRoute, which we already imported. We establish these as private instances because we will only use these variables inside of this component when it is executed. Outside of this, we won’t use them. So we construct them and set them as private to avoid polluting our global variable namespace and also to keep things organized.

We create a getPost function which takes a slug, and calls the getPost function inside of the PostsService. When data is returned from our service, we assign the first result (since we should only be getting one post) to the component variable post that we just declared.

Finally, inside of ngOnInit, we use our instance of route to loop through the params, get the slug, and pass it to the getPost function we just wrote above.

Displaying the Post

Now, when you click a single post in the post-list, we change components from PostListComponent to PostSingleComponent. We change the url, get the slug from the ActivatedRoute, and pass it to PostsService. Inside of PostsService, we get the post by slug, and return it to the post variable inside of PostSingleComponent. All that’s left is displaying the post to the user.

/** post-single.component.html */

<div *ngIf="post">
  <h1>{{ post.title.rendered }}</h1>
  <div [innerHTML]="post.content.rendered"></div>
</div>
<div *ngIf="!post">
  <p>no post found</p>
</div>

*ngIf will make sure a post exists before rendering. If there is no post, we won’t get an error, instead it will just skip this piece of code and show “no post found”.

We’re attaching data to our HTML elements in two different ways here. The WP API 2 is set up for article.title.rendered to return a string of text. We can show this by using double brackets.

In the returned JSON, article.content.rendered is a bit different. If we output this using double brackets it will output a string of HTML. That means it won’t render HTML, but show a string of text, some of which is HTML. In order to render the HTML and show the content inside, we have to bind article.content.rendered to an element, which we do by using [innerHTML]=post.content.rendered.

Launch

Good to go! If you run ng serve and navigate to localhost:4200, you’ll see that when we click on one of our posts, it will navigate to a new page and display the content. If you want to use this on a live WordPress site, in the CLI, run ng build --prod --deploy-url="/wp-content/themes/{THEME_DIRECTORY_NAME_ON_SERVER}/dist/", then upload index.php, style.css, functions.php, and the /dist folder.

Our list of posts are now clickable. Check out the project on GitHub and play around with the code! Thanks for reading.

© 2018