Polymer 3.0 Preview — Building a mini card game

The longer title should be: Experiment with Polymer 3.0 Preview — Building a mini card game with Polymer + Typescript + Webpack.

Webpack and Typescript are not necessarily in Polymer but that’s the tools I familiar with and I like Typescript(probably you should give it a try)!

  • I have no prior experience using Polymer. I’m blogging this from a noob Polymer + normal Angular/Vue developer perspective.

Here is the demo and sourcode:

Why

Being a long time AAV developer (AngularJs, Angular, Vue), I did follow the Polymer news, but didn’t really take a closer look into it(for some reasons) , until last week — during Polymer Summit 2017, the Polymer team announces two big changes:

  1. Polymer is moving from Bower to NPM (with a twist, you need Yarn with a specific configuration).

Suddenly, I feel like “it’s time to try it out!” (I always feel the HTML import syntax looks weird). Probably the marketing slogan works too, #useThePlatform sound welcoming :)!

On a serious note, wouldn’t it be good if you can building performant component using the platform? (Polymer is not a framework. It’s a thin sugar layer to use the web-components)

After going through the Polymer 3.0 Preview hands-on published by the Polymer team, I found a few things that doesn’t look “normal” for a typical AAV developer (me).

  • import statement with .js and import directly from node_modules using relative path — It’s a minimalistic example. However, it’s not how we import in Angular, Vue and React “normally”. It’s the Bower way of import but it’s not how I normally work with modules.

Therefore, I decide to build a Polymer 3.0 project from scratch with the way I used to.

The GIF

Project Setup

Let start with the project setup and the dependencies:

Dependencies

There are very little dependencies:-

  • @polymer/polymer@next — need for polymer

Dev dependencies

Since we are using webpack and typescript, there are a couple of development dependencies

  • typescript

Folder structures

  • build — all webpack configurations here (development and production config are be different)
folder structure in a glance

Important notes

  1. Add a new property "flat": true in your package.json file because Polymer needs it.

The above line means — for all source files that end with .html, import them as string.

Show me the Polymer code!

Hang on! Before we walk through the code, let’s take a quick look at our page. We’ll build 5 components in this project.

app component is the root component, and the other 4 are the child components as shown below.

Each component consists of 2 files:

  • index.ts — Our component logic will live here.

Alright, let’s dive into each of these components.

app component

Let’s look at the app component template, nothing too complicated. We use all 4 mentioned components here:

  1. Whenever user clicks on the reset button in top bar, top bar will fire an event. The parent (in this case, our app component) listens to the event and will call theresetGame handler function. Custom event in Polymer component always start with on-* and must be kebab case. When assigning a handler (e.g. resetGame), you can’t put resetGame(), it should be resetGame (in AAV we can do either).

Here is the TS code:-

The logic is not too complicated, so I wouldn’t bloat this article with all the logics. Few notes here:

  1. Custom element MUST be ES6 class. In our case, we extend our class from PolymerElement. In Angular, we use class and decorator @Component. In Vue, component is not necessarily to be a class, although it could be(I am using that).

top bar component

next, take a look at our top bar component.

2 interesting points here:

  1. Button on-tap event — Polymer provides mixin to handle gesture events. Therefore, instead of click, I use the tap event.

There are 3 things to note here:

  1. The GestureEventListners mixin. In app component, we extend our component from PolymerElement, but this time, we extend it with mixin so we can handle the on-tap event.

Typescript is smart, it can or at least try to “guess” the properties and methods available in the class. In this case, when we call this.dispatchEvent , Typescript cannot find dispatchEvent in the class, so it will throw warning. Why Typescript can’t “guess” in this case? It’s because the parent class PolymerElement didn’t provide type information.

To get around this, there are a few ways, the best is of course Polymer has official type definition file that ship with the library, like Angular and Vue, or we write our own (and contribute it to @types), or simply bypass it by telling Typescript don’t inference (or it can be any type).

I did the later. (this as any) is for that purpose. You can do (<any>this) . alternately. Troublesome? Maybe. I saw a Github discussion on the Polymer definition file, maybe… soon? When it’s in, you wouldn’t need to do the above.

3. The last thing to note is the dispatchEvent. It’s similar to Angular’s eventEmitter and Vue this.$emit . You can fire a custom event with/without parameter. Other elements (e.g. our app component in this case) can listen to the event.

top message component

Top message component display to running time. time is a property passed in by parent component.

The input time is expect to be an object like something this:-

{ hour: 0, minute: 4, second: 15 }

The display format however, expect to be always 2 digits for each unit, e.g 00: 04: 15.

Let’s look at our template:

  1. What we do here is we convert our time object to displayTime object with correct padding.
{{ time.hour | lpadTime }}: {{ time.minute | lpadTime }}: {{ time.second | lpadTime }}

No official solution for that. However, I did found a solution by Addy Osmani — polymer-filters, but I’m not sure how to use that or write that in Polymer 3.0 Preview.

So, here go our Typescript:

  1. Let’s jump straight into the properties getter, since the displayTime is a computed output, we can configured it with the computed property and assign a function to handle that. In our case, timeChanged function will be invoked whenever time changed. I prefer the way Angular and Vue ways on declaring a computed field. Simply with getter or Vue’s computed object.

pop up modal component

Our pop up modal component has nothing special. It’s sort of combination of what we code in top bar and top message.

It accept a time input to display the completion time, and it has a reset button that user can click.

Therefore, we’ll skip this and jump to our main component — cards!

cards component

Our main component.

The card component received an input array of cards and display it accordingly. User can tap on the card and flip it. If two flipped cards do not match, it will be fold back to cover.

The template is straight forward.

  1. We had some pretty long CSS to handle the stacking of the card value and the cover, the flip animation and scale nicely in different device sizes. You can check it out in the source code.

In Angular, I can do something like this:

<div class="card" [class.flipped]="item.isFlipped || item.isMatched">

Alternatively, I can do something like this in Vue:

<div :class="['card', (item.isFlipped || item.isMatched)? 'flipped' : '']">

However, in Polymer, I must do something like this:

<div class$="[[getClass(item)]]" on-tap="flip">

getClass is the function that will evaluate my item and return me the relevant css classes.

Let’s look at our Typescript file:

  1. Let’s look at line number 41 flip({ model }). Remember that the flip function is called by the card’s on-tap event. Normally we will flip function would look like flip(event). The event object has a lot of properties with we don’t need in our logic. What we want here is the event.model property. With ES6, we can utilize the destructing assignment feature. Therefore, we use flip({ model }) .

Register our components

After we wrote all our custom elements, we need to register all of it. The normal way I find in Polymer tutorial(e.g. this tutorial) is at each of the component file, we have this line:

customElements.define('my-top-message', MyTopMessage)

or declare a staticis getter in every class to return the name string, so you can register like this later:

customElements.define(MyTopMessage.is, MyTopMessage)

Please take note that you must register the name as kebab-case , else it WON’T work. Probably I’ve been spoiled by Angular and Vue, we can register component as camelCase and the framework will handle that for us, everything it will be fine.

Besides, I found it a bit repetitive to repeat the above syntax repeat and repeat. Plus, in my case, these elements will be bundled and used together.

So I created an index.ts file in components folder, loop each of the elements and register it using lodash kebab case helper.

Probably not suitable for library distribution, but I am not distributing a library in this case.

Summary: Overall experience (Positive!)

It feel natural to use ES6 Module. I like it. The delta to pick up Polymer is not high if you have knowledge on Angular, React or Vue.

Here come the question:

Should we #useThePlatform (Polymer) or #buildOnUs (Angular)? (sorry, I didn’t know any Vue slogan or hashtag)

For your information, both Angular and Vue are compatible with custom elements, source from: https://custom-elements-everywhere.com/.

Custom elements

I like what the website stated:

Making sure frameworks and custom elements can be BFFs

It’s not an either one option, it can be mixed and match. And that, is something I would like to dive into as well.

Here are the full source code:

Thanks, Happy coding!

TL;DR;

Catchas

  • NPM or Yarn — I’m using Yarn in most of my projects so it’s not really an issue now. One of the reason we switch to Yarn is the package version(well, and the speed). With the the new NPM feature — package lock, I’m trying that to see if we can switch back to NPM.

To do

  • Make uglifyJS works, not sure if it’s related to this Github issue.

--

--

Coder. Diver. Malaysian 🇲🇾. Speak English, Mandarin, Malay, C#, Java, Javascript and more.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jecelyn Yeen

Coder. Diver. Malaysian 🇲🇾. Speak English, Mandarin, Malay, C#, Java, Javascript and more.