Creating PWA with Angular 5. Part 2: Progressifying the application

This is the continuation of my previous article, where we’ve created our first Angular 5 application. And I hope everybody has a successfully hosted project on github-pages.

But if you’re just joining us — it’s ok. To follow the next steps you only need a production-built version of any web application created with Angular 5 hosted on any web server or locally.

Step 1. First audit.

So. We need to figure out how is our app going and where our week points take place. And all we have to do is open our Chrome DevTools and go to the Audits tab, where we can find such a powerful tool as Lighthouse — the best diagnostics of web pages.

Press the big blue “Perform an audit” button to see what we can do.

Lighthouse will offer four options for testing, and I recommend to run through all of them at least for the first time. Ready, steady, run the audit!

My results are not so impressive for now:

It seems that everything is ok with my accessibility and I follow the best practices, but my as a PWA my application s**ks.

Fortunately, I can see what’s wrong:

Also it takes too much time for my users to see at least something:

With those numbers and generated ‘to-fix’ list, we have a start point. So, let’s get it started.

Step 2. Adding the app shell

An application shell is the minimal UI that user will see ASAP. The app shell should load really fast to catch user’s eyes and make him wait for the whole content.

2.1 Loader

The most common example of app shell is loader (or spinner), that is visible for users until app and data is ready.

As an example resource, you can go to the codepen.io and search for something like ‘simple css spinner’ and choose whatever you like. Copy markup and styles directly to your index.html file. Css goes to <style> tag (minifying the css code would be helpful), the html of your loader — inside <app-root> element (it will be removed automatically when angular app will be ready to work).

<app-root>
<div id="spinner"></div>
</app-root>

2.2 Header

To avoid an abrupt jumps in view, we can add some fake header-template.

<app-root>
<nav
style="background-color: #673ab7; height: 56px; width: 100%">
</nav>

<div id="loading"></div>
</app-root>

2.3 JS Fallback

Some of the Lighthouse warning says, that we didn’t provide any content, if scripts are not available. To deal with it, we can use the html tag <noscript>. For example:

<noscript>
<h3 style="color: #673ab7; font-family: Helvetica; margin: 2rem;">
Sorry, but app is not available without javascript
</h3>
</noscript>

2.4 Misc. fixes

Go through your audit results and read all the warnings carefully. I am sure that you will find some things that you can improve or fix outside the pwa world.

For me, it was:

The issue is in static width of image-card, that I’ve set lately. Css media-queries will solve my problem.

Let’s build our project again. Also I recommend you to make some commits during the process, to have an opportunity to look through our steps in the future. Push updates to github pages branch and in a minute refresh the app browser tab (❗️ don’t forget to close dev tools — audit can be rerun only ofter closing the dev tools panel). Make sure that changes were applied, open the dev tools and run the audit test again (you can uncheck the accessibilities and best practices checkboxes to speed it up)

My fresh results:

We’ve lost some points in performance, but in general, the picture looks better.

Step 3. Adding the manifest

As we can see from the audit results, 3 red warnings tell us that we need something called “manifest”.

Web App Manifest is a file inside our application, that provides some descriptive information. From MDN docs:

The web app manifest provides information about an application (such as name, author, icon, and description) in a JSON text file. The purpose of the manifest is to install web applications to the homescreen of a device, providing users with quicker access and a richer experience.

There are a lot of examples of how manifest should look like, but the core info should include app name, short description, urls to icons for different devices, name of start file and theme color. For our test, I created the following:

// src/manifest.json
{
"name": "Progressive Web Cat",
"short_name": "PWCat",
"description": "A funny cat image generator",
"icons": [{
"src": "assets/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
}, {
"src": "assets/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
}, {
"src": "assets/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
}, {
"src": "assets/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}],
"start_url": "index.html",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#673ab7"
}

Create manifest.json under the src folder and copy/paste the code above. Open the .angular-cli.json file and add manifest to assets section:

// .angular-cli-json
...
"assets": [
"assets",
"favicon.ico",
"manifest.json"
]
...

As you can see from the config, we need to include icons of different sizes to our project. For our studying process, just google for it — choose any you like. (Hint: go to the Google search result Image tab, find Tools button, and on the slided down toolbar open the size menu. Choose exact size to clarify the search request). Download images and move them to our assets folder.

❗️ It is important to use .png images for app icons (Thanks to Aymeric Tibere for the clarification)

Go to the index.html file, add the following tags to head section:

// src/index.html
<meta name="theme-color" content="#673ab7">
<link rel="manifest" href="manifest.json">

And that’s it. Build and deploy our updates. Time for new audit test.

Take your time to enjoy those progressive number 🙂

Step 4. Service Worker

The last one. We are at the finish line. The last 3 fails we have are related to absense of something called Service Worker.

Google’s definition:

A service worker is a script that your browser runs in the background, separate from a web page, opening the door to features that don’t need a web page or user interaction.

MDN’s definition:

Service workers essentially act as proxy servers that sit between web applications, and the browser and the network (when available). They are intended to enable the creation of effective offline experiences, intercepting network requests, and taking appropriate action based on whether the network is available, and updated assets reside on the server.

Those two bunches of words can seem confusing, so let’s shed some light on it with practice.

As we understood, we need some js script. Fortunatelly, Angular team made a huge work for us and provide a ready sw script, that we can install in one line:

npm install @angular/service-worker --save

Also we need a ngsw configuration file (Sorry for so many manual work. You won’t do that all the time. I will show you the easy way later. You sit tight until then, okay?).

touch src/ngsw-config.json

Copy/paste there the following code:

// src/ngsw-config.json
{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
]
}
}]
}

Then, we need to enable sw for our project in .angular-cli.json file:

// .angular-cli.json
...
{ 
"apps": [{
...,
"serviceWorker": true
}]
}
...

To register sw in application, add the following line to app.module.ts:

// src/app.module.ts
...
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
@NgModule({
...
imports: [
... ,
environment.production ? ServiceWorkerModule.register('ngsw-worker.js') : []
],
})
...

Commit your changes, build your app and push to gh-pages

ng build --prod --base-href "/PWCat/"
ngh

❗️It’s very important to use prod flag. A base-href is necessary if you are working with gh-pages.

We’ve overcome another barrier! Congrats! :)

The easy way

The steps above are compulsory and in the most cases redundant, because angular CLI can do all the job for us on the build step. Do you remember how we’ve generated our app in the Part 1? When we first started, we didn’t heard about service workers, so we didn’t add the following magic flag for it:

ng new PWCat --service-worker

And that’s it. This flag will install sw npm module, enable sw in .angular-cli.json, register sw for our application and generate ngsw config file with some defaults.

Unfortunatelly, you will not always be the one who starts building the app. And often it won’t include the service worker by default. But now, you know how to deal with :)

Rerun the audit and congratulate yourself with great results:

Use it like a native app

Open your app on your phone device through Chrome browser. Notice the popup at the bottom of the tab.

After pressing the blue button you will find the app icon on the free space of your homescreen

And now you can use it like a native application, you don’t need a browser and searching for it through url anymore.

Offline state

You can notice, that our app is working even if there is no internet connection, but unforunately looks not so good. It lacks information and image card doesn’t please us with any cat face.

To fix that, we need to change the logic of img-card controller a little bit through making our button more customizable. I want my ‘Get new cat’ button be disabled when we are offline and tell the user about the situation. Let’s figure out which attributes can be set with js:

// src/img-card/img-card.component.html
...
<button
color="{{ button.color }}"
(click)="generateSrc()"
disabled="{{ button.disabled }}"
mat-button
mat-raised-button>
{{ button.text }}
</button>
...

Create a new class and define there types for each property:

// src/img-card/img-card.component.ts
...
class Button { 
text: string;
disabled: boolean;
color: string;
}
...

Then create a new public property of ImgCardComponent of type Button:

// src/img-card/img-card.component.ts
...
public button: Button = {
text: 'Give me another cat',
color: 'primary',
disabled: false
};
...

We can check if we are online with global navigator object:

// src/img-card/img-card.component.ts
...
ngOnInit() {
this.src = this.image.api + this.image.message +
'?size=' + this.image.fontsize;
  if (!navigator.onLine) {
this.button.text = 'Sorry, you\'re offline';
this.button.disabled = true;
}
}
...

Last thing we have to do is tell service worker to cache the cat image. In assets section of sw config, add urls property

// src/ngsw-config.json
...
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
],
"urls": [
"https://cataas.com/**"
]

}
}

Again, commit, rebuild and push. Refresh your app, switch off the wifi and reload tab again. No downasaurs anymore — only cute cats :)

And that’s finally all :)

You still can find the source code of my app here. You can test it online (and offline 😉 ) here.

Resources I’ve used and was inspired by:

Web goes Native: Progressive Web Apps with Angular

Angular 5 Service Worker

A new Angular Service Worker

Goolge’s PWA checklist

Thank you for staying with me! 😊
I hope, you’ve found something useful! And sorry for long-read :)
Enjoy the rest of your day!