DEV Community

Cover image for How to add an Add to Homescreen button in your PWA
Bhaskar Nair
Bhaskar Nair

Posted on

How to add an Add to Homescreen button in your PWA

Introduction

As of 2021, PWA features are supported to varying degrees by Google Chrome, Apple Safari, Firefox for Android (not desktop), and Microsoft Edge. This means your PWA can be installed as an app on all modern mobiles and desktops.

This makes PWAs very powerful as a single code base can be used across variety of devices without the need for varied configurations to support different environments (looking at you flutter).

This article will show you how to solve the issue of adding an Add to Homescreen button to your PWA which can show the user a prompt to install your app when required.

This tutorial is using VueJS, but you can use the same code anywhere.

Problem

The Add to Homescreen popup shows up on page load rather than when required.

Solution

Capture the event, store it and show it as required.

Code

We make a component addToHomeBtn.vue which is imported in the entry point of your app. App.vue in this case.

Start by creating a variable to store the event, so it can be used later:

data(){
  return {
   deferredPrompt: null,
  }
 }
Enter fullscreen mode Exit fullscreen mode

Now add the method to capture the event:

captureEvent() {
      window.addEventListener('beforeinstallprompt', (e) => {
        // Prevent Chrome 67 and earlier from automatically showing the prompt
        e.preventDefault()
        // Stash the event so it can be triggered later.
        this.deferredPrompt = e
      })
    }
Enter fullscreen mode Exit fullscreen mode

And now simply call this method in the mounted hook of the component.

mounted() {
    this.captureEvent()
  }
Enter fullscreen mode Exit fullscreen mode

To re-invoke the prompt, simply use the event we stored before.

clickCallback() {
      // Show the prompt
      this.deferredPrompt.prompt()
      // Wait for the user to respond to the prompt
      this.deferredPrompt.userChoice.then((choiceResult) => {
        if (choiceResult.outcome === 'accepted') {
          // Add analyticcs event
          this.$gtag.event('add_to_home_screen')
        }
        this.deferredPrompt = null
      })
    }
Enter fullscreen mode Exit fullscreen mode
<button
     v-if="deferredPrompt"
     ref="addBtn"
     class="add-button"
     @click="clickCallback"
>
     Add
</button>
Enter fullscreen mode Exit fullscreen mode

And, that's done!

Full Component Code

<template>
  <div>
    <button
      v-if="deferredPrompt"
      ref="addBtn"
      class="add-button"
      @click="clickCallback"
    >
      Add
    </button>
  </div>
</template>

<script>
export default {
  name: 'AddToHomeScreen',
  data: () => ({
    deferredPrompt: null,
  }),
  mounted() {
    this.captureEvent()
  },
  methods: {
    captureEvent() {
      window.addEventListener('beforeinstallprompt', (e) => {
        // ! Prevent Chrome 67 and earlier from automatically showing the prompt
        e.preventDefault()
        // Stash the event so it can be triggered later.
        this.deferredPrompt = e
      })
    },
    clickCallback() {
      // Show the prompt
      this.deferredPrompt.prompt()
      // Wait for the user to respond to the prompt
      this.deferredPrompt.userChoice.then((choiceResult) => {
        if (choiceResult.outcome === 'accepted') {
          // Call another function?
        }
        this.deferredPrompt = null
      })
    },
  },
}
</script>
Enter fullscreen mode Exit fullscreen mode

The primary part of the code is pure JS and can be used anywhere. Just make sure you call the event before browser shows the prompt.

Thanks for reading!

Top comments (1)

Collapse
 
wadkan profile image
Wadkan • Edited

Thanks for the description.

Unfortunately, I got this:
_TypeError: Cannot read properties of null (reading 'prompt')
at Proxy.clickCallback (PWAPrompt.vue:43:27)
at runtime-dom.esm-bundler.js:296:60
at callWithErrorHandling (runtime-core.esm-bundler.js:158:18)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:166:17)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:176:17)
at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js:278:5)
_
Any ideas?