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

In this weekend’s 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. If you haven’t seen Part 1, I recommend that you start there. Today, for Part 2, we will convert the Home page list items to cards, which will allow for a richer presentation of the post previews.

Part 1 Recap

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

  • Installed the Ionic CLI
  • Create a new Ionic app with the CLI
  • Ran the app (acid test)
  • Setup a Docker-based WordPress development environment using a docker-compose file and a single command
  • Populated WordPress with dummy data using WordPress plugin
  • Enabled the WordPress REST API
  • Finally, we created a DataService to do the fetching from the WordPress REST API and connected it to the HomePage component to prove that we could render the JSON results.
  • Made source code available on GitHub at https://github.com/codyburleson/ionic-ng-wp-client.

The final result of Part 1 is still pretty simple, but it’s a solid foundation. After Part 1, and starting now, our app looks like this.

Part 2 game plan

The game plan for today is to convert the Home page list, which currently just shows the title and id, into a set of cards which can show much more information such as the title, excerpt, date, featured image, etc. Take a look, for example, at the example cards from the Ionic 4 beta documentation for ion-card.

Something like this is much more like what we want to preview each post, wouldn’t you agree? Great! Let’s begin.

Replace Home page list with cards

Modify 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-card-header>
      <ion-card-subtitle>{{item.date}}</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>
    </ion-card-content>
  </ion-card>
  
</ion-content>

Now, if you run ionic serve, the result should look something like this.

That’s much better already.

There’s something important to point out here, however. Notice, that for the title and the excerpt, we used an Angular technique to render them as innerHTML? We did this with the following two lines.

...
<ion-card-title [innerHTML]="item.title.rendered"></ion-card-title>
...
<div [innerHTML]="item.excerpt.rendered"></div>
...

You might have thought to do it this way instead.

...
<ion-card-title>{{item.title.rendered}}</ion-card-title>
...
<div>{{item.excerpt.rendered}}</div>
...

In the second case, Angular does not interpret and decode special HTML characters. In other words, it will render the raw data, but it will not render the HTML as it should be. The [innerHTML]="" trick fixes this.

Now, moving on, the date format is not user friendly, so let’s fix that.

Format the date using Angular DatePipe

To make the post dates more user friendly, we can simply use the Angular DatePipe.

In home.page.html, we can change <ion-card-subtitle>{{item.date}}</ion-card-subtitle> to <ion-card-subtitle>{{ item.date | date }}</ion-card-subtitle>. This produces the a better date format as you can see below.

That looks good enough for me, but there are many different ways to format dates and some other format might be desired by someone else. For that reason, my desire is to have the rendered date format be configurable. For now, we will allow this to be done in the environment  configuration files where we hold global constants.

Add a dateFormat field to the configuration by changing both src/environment/environment.ts and environment.prod.ts to the following.

// For dateFormat options, see: https://angular.io/api/common/DatePipe
export const environment = {
    production: false,
    endpointURL: 'http://localhost:8080/wp-json/',
    dateFormat: 'MMM d, y'
};

Then, in home.page.html, make sure the DatePipe is changed to be {{ item.date | date:dateFormat }}. All of home.page.html should then look like this…

<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-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>
    </ion-card-content>
  </ion-card>

</ion-content>

And finally, we need to have the dateFormat  field also in the HomPage component. We’ll import the environment file and pass the value through with dateFormat = environment.dateFormat. Modify src/app/home/home.page.ts to the following.

import {Component, OnInit} 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'],
})
export class HomePage implements OnInit {

    items: any[];
    dateFormat = environment.dateFormat;

    constructor(public dataService: DataService) {
    }

    ngOnInit() {
        console.log('> ngOnInit');
        this.dataService.getPosts().subscribe((data: any[]) => {
            this.items = data;
            console.log('ngOnInit() > items: %o', this.items);
        });
    }

}

Now, when users edit the environment file to point to their own WordPress endpoint, they can also configure the date format to their liking. This is a good check-point to make a commit.

Replace Home page list with cards

Add featured image to cards

If a featured image is included with the post, we want that to render in the cards. This is as simple as adding the following line in our cards.

<ion-img *ngIf="item._embedded['wp:featuredmedia']" [src]="item._embedded['wp:featuredmedia'][0].source_url"></ion-img>

Because some items do not have a featured image, we’ve used ngIf to avoid errors. To add this line in the proper place, modify home.page.html to 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>
    </ion-card-content>
  </ion-card>

</ion-content>

Now, if we run ionic serve and examine the result, items with featured images look like this.

Perfect! Again, it’s a good time for a commit.

Add featured image to cards

Wrapping up

Building on Part 1, I showed you how you can dress up the home page with cards instead of ion-list items. We formatted the date for a better user experience and we now render featured posts when the posts have featured images.

Remember that the evolving source code can always be cloned from GitHub at https://github.com/codyburleson/ionic-ng-wp-client.

I hope you’ll join me on this project, contribute comments, or your own improvements. I intend to take the project further and will continue my “developer log” here, so stay tuned!

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

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