How to create a progressive web app featuring Angular and headless CMS
--
Have you ever wondered how a headless Content Management System fits in with Progressive Web Apps?
I recently read my colleague Bryan’s story about Progressive Web Apps. The article talks about the implementation of a Progressive Web App (PWA) that lists interesting places stored in the headless CMS.
You could install this app on your device. It uses a service worker to cache the application and data about the points of interest. The application was written in plain JavaScript.
Having written a good share of JavaScript code, I wanted to expand on the concept using more complex frameworks.
I narrowed my choices down to three big players — React, Vue, and Angular. I chose to use Angular, because it has already support for service workers, and I wanted to use TypeScript.
Each step of this tutorial will be accompanied by a link to a GitHub commit. This way, you’ll always be able to see what the code looks like.
To run the app, just download or clone the commit and run npm install
and ng serve -o
. The whole code is stored in one of the branches.
Let’s get to it!
Prerequisites
- node.js v8+
- Angular CLI v.1.7.4 installed as a global dependency via the npm package manager:
npm install -g @angular/cli
Getting started
First of all, generate a new project. You can easily generate all boilerplate code using the awesome Angular CLI tools. Just navigate to a folder and generate a ready-to-run code:
ng new cloud-sample-angular-pwa-aps
Boilerplate configuration
There are a few steps to configure the boilerplate.
The generated code uses plain CSS by default. But, you might want to make your life easier with SCSS. To achieve this, perform these steps:
- Set
defaults.styleExt
value fromcss
toscss
in the/.angular-cli.json
configuration file - Rename
styles.css
tostyles.scss
- Rename
/src/app.component.css
to/src/app.component.scss
and reflect this renaming inapp.component.ts
in the component declaration atribute’sstyleUrls
property value.
Create some initial content for the app
- Global styles: /src/styles.scss
- Component: /src/app/app.component.html and /src/app/app.component.scss
Lets have a look!
Just run this command:
ng serve -o
Load the data
Let’s finally use the power of Angular. In this section, we will define an injectable client that allows the app to get Kentico Cloud data. I will use the same data source as Bryan used in his article.
First of all, install Kentico Cloud Delivery SDK via the following command:
npm install -P kentico-cloud-delivery-typescript-sdk
Then, create a client provider that will be used in dependency injection.
Create a new file in the /src/app
folder and name it delivery-client.provider.ts
. This provider module needs to export an object defining the factory used to create our client. In the code below, you can see the ID of the project in Kentico Cloud where the data is stored.
import { DeliveryClient, DeliveryClientConfig } from 'kentico-cloud-delivery-typescript-sdk';export const DeliveryClientFactory = (): DeliveryClient => {
const projectId = '975bf280-fd91-488c-994c-2f04416e5ee3'; return new DeliveryClient(
new DeliveryClientConfig(projectId, [])
);
};export const DeliveryClientProvider = {
provide: DeliveryClient,
useFactory: DeliveryClientFactory,
deps: []
};
Next, edit app.module.ts
. This is the place where you state which modules are loaded.
...
import { DeliveryClientProvider } from './delivery-client.provider';
...@NgModule({
...
providers: [DeliveryClientProvider]
...
})
Now we are ready to use the client in the app component.
We will set up the app.component.ts
to use the DeliverClient
that is auto-magically injected as a parameter to the constructor. We’ll also subscribe the component to the client’s observable and we’ll define a corresponding observer action.
import { Component, OnInit, OnDestroy } from '@angular/core';
import { DeliveryClient, ContentItem } from 'kentico-cloud-delivery-typescript-sdk';
import { Subscription } from 'rxjs/Subscription';@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})export class AppComponent implements OnInit, OnDestroy {
dataSubscription: Subscription;
pointsOfInterest: ContentItem[];constructor(private deliveryClient: DeliveryClient) { }ngOnInit() {
this.dataSubscription = this.deliveryClient
.items<ContentItem>()
.type('point_of_interest')
.get()
.subscribe(response => {
this.pointsOfInterest = response.items;
});
}ngOnDestroy(): void {
this.dataSubscription.unsubscribe();
}
}
The last step is to display the data from CMS using the Angular ngFor
directive to iterate through the items and render them.
<header>
<h2>Pack and Go</h2>
</header>
<main class="main">
<div class="card" *ngFor="let poi of pointsOfInterest">
<h2 class="title">{{poi.title.value}}</h2>
<div class="content" innerHTML="{{poi.description.value}}"></div>
<a class="map-link" target="_blank" href="http://maps.google.com/?ie=UTF8&hq=&ll={{poi.latitude__decimal_degrees_.value}},{{poi.longitude__decimal_degrees_.value}}&z=16">
Open the map
</a>
</div>
</main>
Allow adding a shortcut icon
Now, we’ll make the app capable of adding its icon to the desktop or start screen of the device.
This step is quite easy. It requires us to create a JSON file containing metadata about the app and link it from the head
tag. The manifest file should point to multiple URLs of icons in various sizes.
We should also list the manifest.json
file in an assets declaration in the .angular-cli,json
configuration file.
{
...
apps: {
assets : [
...,
"manifest.json"
],
...
},
...
}
But, more importantly, link to the manifest.json
file from index.html
.
<link rel="manifest" href="manifest.json" />
Finally, we’ll create the manifest itself, together with all the icon renditions. Take a look at the link below to see the result.
Set up the service worker
The concept of the service worker is what makes PWA apps revolutionary.
Service workers work as a proxy between the client and the internet. Depending on the actual configuration, the service worker can pre-cache the app skeleton (called the ‘app shell’) during the first load. This means that subsequent requests are blazing-fast. The service worker can also silently cache all other application data.
First of all, it is required to install the service worker module to the application.
npm install -P @angular/service-worker
Now enable the service worker in Angular in the .angular-cli.json
configuration file.
{
...
apps: {
"serviceWorker": true,
...
},
...
}
Now, let’s import the service worker module to our app using the app.module.ts
file.
...
import { ServiceWorkerModule } from '@angular/service-worker';
...
@NgModule({
...
imports: [
...
ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production })
],
...
})
˛...
The last thing is to configure the caching strategies for the app shell and the data. First we need to create ngsw-config.json
configuration file under the /src
folder.
For the app shell, we’ll use the default set up described in the documentation. This configuration will pre-fetch index.html
, the favicon.ico
, and the app shell, including the linked CSS and JavaScript bundles. Files in /assets
folder are lazy-loaded.
Requests for the data from Kentico Cloud will use another caching strategy. We will define an API endpoint as a new data group and set the caching to use the freshness strategy. In the commit link bellow, you can see the whole content of the configuration file.
Now we are ready to install the app on the device. For instance, in Chrome in Android, you can do so by tapping the ellipsis glyph and choosing “Add to Home screen”.
All right, we’re done. Despite a quick and simple implementation, the app is quite powerful and fast. And we’re free to extend it in various ways, like importing the material design or font icons.
The PWA APIs also allow us to use cool native features such as:
- read device’s sensors
- display push notifications
- and use the device’s cameras.
Our app could also sense when the device transitions from online to offline, and vice versa. We could also use the automatically generated, strongly-typed models of content items from the CMS.
As you can see, creating a PWA in Angular is easy, yet allows us to extend the app much further.