The concept of Progressive Web Apps (PWAs) is a framework agnostic approach which seeks to combine discoverability and accessibility of a website with the functionality of a native app.
Since couple of years I see an increasing interest technologies which bridge the gap between web- and native-apps.
In 2018 PWAs have made a great step towards mainstream adoption. By now, plenty of companies like Pinterest, Uber, Twitter, Trivago, The Washington Post, Starbucks, have already created PWAs to run parallel to their native apps.
The reason is obvious, plenty of these companies report very promising numbers, mostly as astonishing as the 97 percent of increase in conversions Trivago has seen.
Why should we start developing PWAs now?
In fact, in 2018 also the majority of browser vendors started backing the technology behind PWAs. Microsoft committed to bring PWAs to “more than half a billion devices running Windows 10”. Google even went as far as calling it the future of app development — no surprise that Lighthouse, Google’s tool for improving the quality of web pages, audits ‘PWA’, next to ‘SEO and ‘Accessibility’ of webapps. And even Apple has finally started to support PWAs in 2018, even though, PWAs are a clear threat to Apple’s app store business.
This article is the first part of a tutorial series that teaches you how to develop a PWA in VueJS!
Part I — Build the Tax Calculator App in VueJS.
Follow me on Medium and you get a notification when Part II will be released!
To make it easy for you to follow, check out this repo. You can checkout a branch for each section!
In this tutorial we will build an income tax calculator. Why?
Because calculating income tax (at least in Germany) is complicated and people would love an app that solves that problem for them. Besides that, it’s also a opportunity to explore the impact of the PWA features mentioned above.
We will use VueJS for this tutorial, as it comes with a great template which makes it easy to kick off a PWA project. Another reason is, that VueJS is really easy to learn. No prior experience in any other frontend framework required!
Enough theory for now, it’s time to get our hands dirty!
We start-off with creating the basic setup and the file structure of our app. To speed things up, we will bootstrap the app with vue-cli. First, we need to install the vue CLI tool globally.
yarn global add
Now we can instantiate the template by
vue init pwa vue-calculator-pwa
We will be prompted to pick a preset — I recommend the following configuration:
? Project name
vue-calculator-pwa? Project short name: fewer than 12 characters to not be truncated on homescreens (default: same as name)
vue-calculator-pwa? Project description A simple tax calculator? Author Fabian Hinsenkamp [email protected]? Vue build runtime? Install vue-router? No? Use ESLint to lint your code? Yes? Pick an ESLint preset Standard? Setup unit tests with Karma + Mocha? No? Setup e2e tests with Nightwatch? No
Vue build configuration we can choose the smaller
runtime option as we don’t need the compiler as the html we write inside our
We don’t add tests here for brevity reasons. If we would set up a project for for production definitely add them.
yarn to install all dependencies. To start the development mode just run
Don’t get confused by all the files in the project — For now we won’t touch most of them. If you want to learn more about the template’s structure check out the documentation. I can only recommend it!
In the project we will find files with the
.vue extension. It indicates that this file is a single-file vue component. It is one of the Vue’s features. Each file consists of three types of blocks:
<style>. That way, we can easily divide the project into loosely-coupled components.
Let’s start creating all the
*.vue files our app consists off.
Create the file
src/App.vue. It is our main view and it will contain our different components which make up our calculator.
Next let’s create the inputForm file
src/components/InputForm.vue. It will handle all user inputs required to calculate the income taxes.
Moreover, we create
.vue skeleton files for the following
Input components including a style sheet named identically to the component it belongs to. All of them belong into the
It will display the results of our calculations.
The panel is a simple component that wraps the input and result components.
Finally, we should remove the
Hello.vue file, that comes with the vue template.
Next, we add the following libraries to support sass/scss files.
yarn add node-sass sass-loader -D
For now, the scss files we added are all empty.
I won’t talk about styles in this tutorial as there is nothing specific about applying styles to app.
Hence, you have two options, create your own styles or checkout the following branch of the github project.
git checkout 01_skeletonApp
We also need logic to calculate our income tax. I use the real German income tax formular. To spare the details, I also added it to the branch.
It also contains some css animations for the input validation message. In case you don’t want to use the branch above, you can also add them manually:
yarn add animation.css
Now we can start coding! To warm you up, we start with building the panel component. It’s good practice to keep such components generic so it can be reused holding any kind of content. That’s why we aim to pass the headline as well as the html for the body to the component.
Let’s add the following code to the template section of the
For one-way data binding in VueJS, we can use textinterpolation. That’s exactly what we do to render the headline. Therefore, we simply need to wrap our headline data object in double curly braces. Attention, this “Mustache” syntax interprets data always as plain text, not HTML.
That’s why we also use vue’s slot element, which allows us to render child elements of our panel component within the body element. Now we are done with the html for the panel component, next we define our script logic.
First, it’s important to add a name to the component so we can actually register the component and import it later on. As we want to pass the headline to the panel, we should specify it as properties. Properties are custom attributes we can register on a component.
To see the panel in our app, just add the code above to our
In the script block we already import the component so adding the html is all we need to add our first component to the app!
We should see the panel when we run
If we have any problems implementing the panel or want to skip this section check the following branch.
git checkout 02_panel
Next we build our input form with some neat custom input validation.
We have three types of inputs: regular, select and radio.
Except the radio buttons these inputs need input validation and corresponding user feedback. To avoid repeating ourselves, we should build a reusable input component. To build a component which is actually reusable, I advice to build it in a way, that allows to easily extend it without changing the whole architecture.
Defining a clean and thought through component api is a great starting point.
In our case, we always want to control four properties from outside of the component:
Let’s translate these requirements into code! The
input.vue component looks like the following:
We add some custom vue-attributes to the native input component. First we add
v-if, which allows us to render the input only if we pass the correct type to our component. This is important to add different types of inputs. Next, we bind to the component’s
input event with the
@-prefix to a method called
Thats where our custom input validation comes to play. We add a validation library to the project by running
yarn add vee-validate and register the plugin in our
Our validation consists in intercepting the native input event and then check if the entered values meet our validation rule. In case it doesn’t we set an error message. Therefore, we add two methods to the
input.vue file. The
customInput method is triggered when the user enters any input.
The validation error message is returned from the
v-validate plugin. We only have to add some html to show it to the user:
I add a transition to the error message. VueJS comes with a transition wrapper, combined with the flip-animation from
animate.css and some styles, we can get a nice error message without any hassle.
To add the new input to the app, register the completed Input component to the
InputForm.vue. Here we apply two-way-binding through
v-model — It automatically picks the correct way to update the element based on the input type. Now, we need to open
App.vue , import
InputForm as we did with
Panel and replace
<span> content goes here. </span> with
The result should look like what you see here on the left side.
Check the branch for more details!
git checkout 03_basicInput
Now that we have a basic input with validation in place, it’s easy to extend our input component with the remaining two input types — the select and radio input.
For the select element we use an out-off-the-box component. We simply add it by running:
yarn add vue-select
Before we can use it, it needs to be registered in the
main.js file similar to the
v-validate plugin before. However, this time we use the
Now simply add the component to our
Input.vue file. The options we want to show in the dropdown will be passed to the component as props.
Now there are only the radio buttons left to add.
We start off with the native html element. Even though we just need two radio buttons atm, I advice to build the component in a way that allows to pass an arbitrary number of inputs. Therefore, we simply use vue’s
v-for attribute to loop over the options property and create a radio button for each element of the option array.
Additionally, we need to pass the currently selected value in order to manage the ‘checked’ state of the radio button inputs. In the script block we add an array holding all possible value types.
To test our new input types, we need to actually add them to the submit form and pass options to the select and radio input.
Check out the
inputForm.vue file in the following branch to see how the options are passed to the new inputs.
git checkout 04_completeInputs
It follows the same pattern we have investigated for the regular input in detail.
Most importantly, keep in mind to always pass an object containing a value and a label.
That’s it! We managed to create a component that allows us to add all the input types we need and validate them without repeating ourselves!
Now we can finalise the input form. The only thing missing is the button to submit the form.
We start with preventing the default html form event, and call our custom method
There we clean our input results — Although our dropdown and radio buttons require return objects with label and value, we are only interested in the value to calculate the results.
Finally, we create a custom event which emits only the values of our input options.
We also create a computed property which enables our “calculate” button only after all required data is entered by the user.
You find the completed input form on the branch I mentioned already above.
git checkout 04_completeInputs
Now we are already able to get the inputs from the user, next we need to actually calculate the resulting income taxes. As I mentioned in the very beginning, I want to spare you the details about how the different types of deductions are calculated. In case you are interested anyhow check out the
Most importantly, we understand that we use the custom
submitted event to pass the inputs back to our
app.vue component. Here we also calculate the actual taxes and store the resulting values with labels, and add negative signs to the values we deduced from the gross income.
— Why? It makes displaying the results very simple as we will see in the next section.
Now we have everything we need to finally show the results of our tax calculations.
Therefore, we use a native html table to show the label and the corresponding value in a structured manner.
The implementation is quite simple, as we can stick to what we have learned about VueJS already. In fact, we repeat what we have done for the radio input already.
We just pass our calculations as props to the
results component and loop over our results object.
As users probably want to perform multiple calculations without refreshing the page, we add another button, that leads users back to the input form.
Therefore, we simply emit a custom event called
clearCalculations to clear our calculations property in the parent component. Finally, we are also tying all our components together and complete our income tax calculator.
As always, checkout the branch I you want to have a more detailed look at the code.
git checkout 05_result
In this last section there is only, two things left to do — complete the data-flow and manage the lifecycle of the input and result component accordingly.
Result.vue component we just added a button which emits the
clearCalculation event. On the left, we see our main component
App.vue. Here we subscribe to the event and reset the calculations object to be empty.
Now, we want to only render the input or the result component.
Therefore, we add another
computed boolean property, which checks if we have calculation results or not.
Now, we add
v-if attributes to our components based on our
Try it out! Now we should see the table with the results only after we have successfully entered our inputs.
To make the switch between input and results less harsh we add a transition. As we are replacing the one with the other component here, we use the
out-in so that, the current element transitions out first, then when complete, the new element transitions in.
We completed the tutorial! Well done! The branch with the finale application code is the following
git checkout 06_complete
We have successfully kickstarted the implementation of our income tax calculator PWA based on vue’s PWA template. Thereby, we familiarised ourselves with the syntax and paradigms of VueJS.
So far so good but we are still far-off from providing a user experience close to a native app. This is what Part II of this tutorial will focus on.
If you want to be notified when Part II is released — just follow me here on Medium or follow me on Twitter @Fa_Hinse!