Build a PWA with Ionic, Angular, and the WordPress REST API – Part 3

In this weekend side-hustle series, we’re building a Progressive Web App (PWA) for desktop and mobile that delivers WordPress content using Ionic, Angular, and the WordPress API. In Part 1, we got everything setup and working. In part 2, we created a richer presentation of the WordPress Posts using Ionic cards. Today, we’ll implement the single post view (a.k.a. the “content detail view”).

Part 2 Recap

Here’s a quick recap of what we accomplished in Part 2.

  • Replaced Home page list with cards
  • Formatted the post dates using the Angular DatePipe
  • Added featured image to cards (when featured images exist on the WordPress post)
  • Made revised source code available on GitHub at https://github.com/codyburleson/ionic-ng-wp-client.

The final result of Part 2 is still pretty simple, but it’s evolving. After Part 2, and starting now, our app looks like this…

Part 3 game plan

The game plan for today is to create a Read More… button on each post preview card. We’ll also create a PostPage component for viewing a single post and we’ll make a parameterized route for it. We’ll pass the post slug as a parameter to the DataService and use that to retrieve the single post for display on the newly created Post page. While we’re at it, we’ll also polish the presentation a bit more.

Polish the presentation

Right now, there is no white space between paragraphs in the post preview cards, which looks crappy. We can fix that by editing the style of the Home page component. We’ll start by specifying the HomePage component’s view encapsulation type. In Angular apps, component CSS styles are encapsulated into the component’s view and don’t affect the rest of the application. To control how this encapsulation happens on a per component basis, you can set the view encapsulation mode in the component metadata. The view encapsulation type I want to use (for now, anyway) is  ShadowDom, which will ensure  that the styles are specific to the component. We can always change this later if we change our mind.

Open src/app/home/home.page.ts and add encapsulation: ViewEncapsulation.ShadowDom to the component metadata as shown below. The body of the HomePage component is omitted in the example below for brevity.

import {Component, OnInit, ViewEncapsulation} from '@angular/core';
import {DataService} from '../shared/data.service';
import {environment} from '../../environments/environment';

@Component({
    selector: 'app-home',
    templateUrl: 'home.page.html',
    styleUrls: ['home.page.scss'],
    encapsulation: ViewEncapsulation.ShadowDom
})
export class HomePage implements OnInit {

// ...

}

Next, edit src/app/home/home.page.scss with the following contents.

ion-card p, ion-button {
  margin-bottom:16px !important;
}
/*  Hide WordPress Read More... */
p.link-more {
  display:none;
}

This adds the white space we want between paragraphs on the post preview cards. It also adds some margin around the Read More… button that we’ll be adding and it hides any default Read More… link that  is identified with the .link-more CSS  class.

Add a Read More… button

Next, we’ll add the Read More… button to each post preview card. At the same time, we’ll put in a place holder for displaying the post categories and tags, which we may implement a little later down the road. Update src/app/home/home.page.html with the following…

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <ion-title>
      Home
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>

  <ion-card *ngFor="let item of items">
    <ion-img *ngIf="item._embedded['wp:featuredmedia']" [src]="item._embedded['wp:featuredmedia'][0].source_url"></ion-img>
    <ion-card-header>
      <ion-card-subtitle>{{ item.date | date:dateFormat }}</ion-card-subtitle>
      <ion-card-title [innerHTML]="item.title.rendered"></ion-card-title>
    </ion-card-header>
    <ion-card-content>
      <div [innerHTML]="item.excerpt.rendered"></div>
      <div>
        <ion-button href="/{{item.slug}}" routerDirection="root" color="primary" size="small">Read more...</ion-button>
      </div>
      Posted in W<br/>
      Tagged X, Y, Z
    </ion-card-content>

  </ion-card>

</ion-content>

Now, if you run ionic serve and preview the app, you can see that we have margin between paragraphs and a Read More… button as shown below.

Notice that we have a place holder for post categories and tags below the Read More… button. I’m not entirely sure that’s where I want  it yet, but it’s a reminder for us to think about implementing later.

Add the PostPage component

Next we’ll use the Ionic CLI to generate a PostPage component. This will be the view that is loaded when the user clicks the Read More… button. Within the project root, enter the following command.

ionic generate

From the command-line menu of options, choose Page for  the component type and for the Name/path of page:, type Post and hit enter. The Ionic CLI will generate a post directory with the following files inside:

  • post.module.ts
  • post.page.html
  • post.page.scss
  • post.page.spec.ts
  • post.page.ts

We’ll come back to this component soon, but first, let’s modify the DataService so that it can return a single post by slug.

Update the DataService

Modify shared/data.service.ts by adding the following function for the PostPage component to call.

getPostBySlug(slug): any {
    return this.items.find(item => item.slug === slug);
}

This takes the post slug and passes it through an RxJS find function.  RxJS find searches for an element that matches the conditions defined by the specified predicate, and returns the first occurrence within the entire Observable sequence. In this case, we’ll be searching through the array of posts that we already have loaded – returning the one that has the given slug. The slug is the URL segment that identifies the post. We use it to maintain clean URLs that match the WordPress Post name (/%postname%/) permalink structure that we chose in Part 1.

Update the PostPage component

Modify  src/app/post/post.page.ts to be as follows.

import { Component, OnInit } from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {DataService} from '../shared/data.service';

@Component({
  selector: 'app-post',
  templateUrl: './post.page.html',
  styleUrls: ['./post.page.scss'],
})
export class PostPage implements OnInit {

    item: any;

    constructor(private route: ActivatedRoute, public dataService: DataService) { }

    ngOnInit() {
        const itemSlug = this.route.snapshot.paramMap.get('slug');
        this.item = this.dataService.getPostBySlug(itemSlug);
    }

}

In the ngOnInit() function, we set  the item field based on the item given back from the DataService. This is done by passing the item’s slug which is a route param that we can read out of the ActivatedRoute. So now, we just need to configure the parameterized route.

Configure the parameterized route to the Post page

When we used the Ionic CLI to create the PostPage component, it automatically added the following entry to the routing module (src/app/app-routing.module.ts).

{ path: 'Post', loadChildren: './post/post.module#PostPageModule' }

Change the path from 'Post' to ':slug'. The colon signifies a named parameter, which we read by name from the ActivatedRoute.

The entire app-routing.module.ts file should  now look like this:

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';

const routes: Routes = [
    {
        path: '',
        redirectTo: 'home',
        pathMatch: 'full'
    },
    {
        path: 'home',
        loadChildren: './home/home.module#HomePageModule'
    },
    {
        path: 'list',
        loadChildren: './list/list.module#ListPageModule'
    },
    {path: ':slug', loadChildren: './post/post.module#PostPageModule'}
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule {
}

And that’s it! If we run the app now with ionic serve, we can click on the Read More… button in a post preview card to load the full post. Here’s what one of our dummy content items looks like now, loaded up into the Post view.

I’m committing this check-point to GitHub with the following message.

Implement single post view

Wrapping up

Building on Part 2, I showed you how you can implement the single post view (a.k.a. the “content detail view”). So, now we can preview a list of WordPress posts and then read any individual one. However,  at this point we’re only able to access the default number of posts returned from our REST API call,  which is ten. In the next part of this series, we’ll implement Ionic’s Infinite Scroll (ion-infinite-scroll) to automatically fetch more posts when the user scrolls to the end. So, stay tuned and remember that the evolving source code can always be cloned from GitHub at https://github.com/codyburleson/ionic-ng-wp-client.

Wanna keep goin? Click the link below for Part 4:

Build a PWA with Ionic, Angular, and the WordPress REST API – Part 4