Skip to main content

Ionic + StencilJS = Slot Machine


Ionic + Stencil

Hi, this is Mike Callaghan, and today I want to make a simple slot machine. This is a follow up to my recent video on creating my very first stencil web component. In that video, I added the component to an existing ionic app, but I really glossed over how that happens.

Today I'm going to go through the steps necessary to take an existing StencilJS web component, and create a working mobile app from scratch with the Ionic Framework that uses three instances of that component.

The StencilJS component

Let's start with the web component. I created it a few weeks ago to see how to get CSS 3D animation working with Stencil. My goal was to create a component I could use multiple times on a page, without the component needing to know or care what the images are that it's animating, or how many there are. All of that is determined dynamically.

Show the github repo

I'm not going to go through the code here at all. Here is the public GitHub repo if you want to check that out for yourself. https://github.com/walkingriver/slot3d-stencil

Build the component

The important things for me to show is how to modify the existing StencilJS component from the starter template, so that I can build and deploy it into my Ionic app.

Modify stencil.config.js and package.json, according to the instructions at https://stenciljs.com/docs/distribution.

stencil.config.js
namespace: 'wr-slot3d',
generateDistribution: true,
generateWWW: true,

These three options are reasonably self explanatory. For more details, you can take a look at the documentation here. In order to avoid naming collisions with your web components, you'll want to specify a namespace that no one else is likely to use. I chose wr-slot3d.

The next line tells the stencil compiler to build a distribution package. The final line tells it to continue generating a WWW folder. I found that if I didn't do that, I could no longer run a local version.

package.json
"name": "@walkingriver/slot3d",
"private": true,
"version": "0.0.1",
"description": "Walking River Slot3d",
"main": "dist/collection/index.js",
"types": "dist/collection/index.d.ts",
"collection": "dist/collection/collection-manifest.json",
"files": [
  "dist/"
],
"browser": "dist/wr-slot3d.js",

Over in Package.json in the stencil project, we have a little more to do, but it's pretty straightforward. The first four lines are just me changing the stencil template values into something specific for my project. The next four values -- main, types, collection, and types -- are specific to the distribution, but should never change. You can simply set them to these exact values.

The last one, browser, is the main entry point of your component, and will vary depending on the name of your component. In my case, it's wr-slot3d.js at the top of the dist folder.

Once we've modified those two files, we can build a deployment package, which we'll then copy into the Ionic app. We do that with a simple npm run build.

Create a blank mobile app

To get the component into a fresh ionic app, the first thing to do is create one, using the command below. I'm creating an app call "slots" using the "blank" template.

ionic start slots blank

Opening the home page, I'll add a grid with 3 columns and one row for the slot machine elements. We'll fill in the custom elements later.
<ion-grid>
 <ion-row>
   <ion-col>
     1 of 3
   </ion-col>
   <ion-col>
     2 of 3
   </ion-col>
   <ion-col>
     3 of 3
   </ion-col>
 </ion-row>
</ion-grid>

Next, I'll add a full-width button to the main page that says "Spin"
<button ion-button block (click)="spin()">Spin!</button>

Copy the important files to the ionic project

Create web components folder

We'll need a place to store the build elements that stencil generated for us, so I'll create a new directory called web-components in the root directory of my ionic project.

mkdir web-components

Copy entire web components dist folder to slots/web-components/wr-slot3d

Next, I'll copy the entire stencil dist folder into web-components, renaming dist in the meantime to something more meaningful, like wr-slot3d.

cp -r ../slot3d-stencil/dist/ ./web-components/wr-slot3d

Copy web-component's assets/images folder to ionic app's src/app/assets

I also have some images in my stencil demo project that I'll want to use. They can be any images, but in this case, they are some pics of the digits 0 through 9.
cp -r ../slot3d-stencil/src/assets/images/* ./src/assets/imgs/

Update package.json to include the web component automatically.

The next thing I have to do is tell the ionic build process to copy the web components folder whenever it builds. There are a couple of ways to do this, but this one seems the most straightforward and least likely to break in the future.

In package.json, I'll add a config section that the ionic cli will read during builds. I'm going to tell it to use a custom copy script instead of its default.
"config": {
  "ionic_copy": "./scripts/custom-copy.js"
},

Add new copy script

That means I need to create that custom copy script and its scripts directory.

Create scripts folder in ionic app

mkdir ./scripts

Create scripts/custom-copy.js

touch ./scripts/custom-copy.js

The script itself is pretty simple. First, I copy the existing the existing default copy instructions by requiring it from the ionic app scripts copy config file. This should help guard against minor changes to the default config in the future. As long as its location and format stay the same, this should work fine. Then I simply add my own section to it to tell it to copy the web-components folder from the root folder to the www destination.

const existingConfig = require('../node_modules/@ionic/app-scripts/config/copy.config');
module.exports = Object.assign(existingConfig, {
copyWebComponents: {
  src: ['{{ROOT}}/web-components/**/*'],
  dest: '{{WWW}}/build/web-components/'
}
});

Modify the angular module to import the component

In order for Angular to understand that we're going to have custom elements, we need to add the following code to our app module.

schemas: [
  CUSTOM_ELEMENTS_SCHEMA
],
Which also requires importing the identifier into the file, which vs code does for me.

Add the component to the view

Now back in the home, I'm going to add three identical slot components, one in each column. The slot1, 2 and 3 attributes are so we can use ViewChild inside the component to reference them, which I'll do now.

<wr-slot3d #slot1>
<wr-slot3d-item image-url="./assets/images/9.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/8.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/7.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/6.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/5.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/4.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/3.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/2.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/1.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/0.png"></wr-slot3d-item>
</wr-slot3d>


<wr-slot3d #slot2>
<wr-slot3d-item image-url="./assets/images/9.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/8.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/7.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/6.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/5.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/4.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/3.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/2.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/1.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/0.png"></wr-slot3d-item>
</wr-slot3d>


<wr-slot3d #slot3>
<wr-slot3d-item image-url="./assets/images/9.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/8.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/7.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/6.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/5.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/4.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/3.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/2.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/1.png"></wr-slot3d-item>
<wr-slot3d-item image-url="./assets/images/0.png"></wr-slot3d-item>
</wr-slot3d>

Bind angular component click handler

This is all of the component code needed to make it work. The ViewChild decorators let me reference the elements as variables in my component. I could have also used getElementById, but this lets me do it in a slightly more declarative manner. One thing I don't like about this approach is that my slot variables are weakly typed. If you look at the typings file generated by stencil, the implication is that I can get a strongly-typed wr-slot3d elements, with code completion and everything. However, I couldn't get it to work, so I punted and did this instead. I still wrestled with it for awhile, but once I realized I had to get a reference to the nativeElement, all was well.

import { Component, ViewChild } from '@angular/core';

@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
@ViewChild('slot1') slot1: any;
@ViewChild('slot2') slot2: any;
@ViewChild('slot3') slot3: any;

spin() {
  console.log(this.slot1);
  this.slot1.nativeElement.spin(1500);
  this.slot2.nativeElement.spin(2000);
  this.slot3.nativeElement.spin(2500);
}
}

Add Script to index.html

There is one last piece of the puzzle we need to fit into place. Inside of index.html, we need to include the web-component's source file. Remember that we automatically copied the web components folder, but the app still needs to know about the scripts it contains. This would be a good place to introduce another automated build step, but for now I'll just use a script tag.

<!-- Put our web components here -->
<script src="build/web-components/wr-slot3d/wr-slot3d.js"></script>

Now let's fire it up with Ionic serve and see how it looks. As you can see, all three slot elements rendered as expected. And clicking the spin button causes the animations to start, just like a real slot machine.

I plan to use this component again when I rewrite my Mario Kart 8 app as a progressive web app. Subscribe or check back for that's done.


And please follow me on Twitter, @walkingriver.

Comments

Popular posts from this blog

How to copy your Frozen Free Fall progress to a new phone

It's happened to all of us. You are about to get a branch new smartphone, when it hits you. You're on level 250 of Frozen Free Fall. If you get a new phone, you'll lose all of that progress! Ok, admittedly this isn't the most pressing problem of our time, but it's annoying. So today I decided to do something about it.

I have a Samsung Galaxy S4 (Android), and just received an iPhone 5c. Before you bash me on my phone choice, let me explain that the iPhone is provided by my employer at no cost to me. Now you may proceed to bash me for putting games on my work phone. 
First step: Frozen Free Fall had already been installed on both devices. Next, using the Astro File Manager on my Galaxy, I searched and found the Frozen game save stored in /storage/sdcard0/Android/data/com.disney.frozensaga_goo/files/user.dat. I imagine it will be in the same location on any Android phone. If not, just look for user.dat in a folder with a similar name. So, using a USB cable and the A…

Ionic vs. Bootstrap - for a Web App

Ionic 1.x vs Bootstrap 3.x for a Web AppI was recently asked at work to come up with a comparison between Ionic Framework and a more traditional Angular/Bootstrap combination to create a web app. The application will primarily be used in a desktop web browser (probably Chrome or IE). There are also some use cases where it will be accessed from Safari on an iPad. However, this is purely a web; there are no plans to install the app onto the iPad as a hybrid app. Thus, recommending Ionic to build the UI hadn't occurred to me until the request was made.

This is even more surprising in that I recently published a Pluralsight course on Ionic Framework 1. It should have been the first thing that crossed my mind.
One constraint is that currently only Angular 1.x and Bootstrap 3 are authorized web technologies. Ionic 1.3 was recently approved, but not Ionic 2, Angular 2, or TypeScript yet.
Given those constraints, herein is my attempt at coming up with reasons to use (or not to use) Ionic…

Windows 10/Server 2016 100% Disk on BootCamp and Parallels!

I've been wrestling with Windows on my 2014 Mac Mini for more than two years. Soon after I bought the Mac, I made a 200GB BootCamp partition and installed Windows 7. I also bought Parallels 10 Desktop and pointed it at the BootCamp partition. It was great. I had a convenient VM when I needed something quick. I also had BootCamp when I needed native performance. Not long after that, I upgraded it to 8.1. Then sometime later, Windows 10.

I don't remember exactly when it happened, but one day I fired up Parallels and my entire system ground to a halt. Shutting down the virtual machine caused everything to go back to normal, so I figured the problem was with Parallels. So I upgraded it. Same problem. I made sure Windows was up-to-date, thinking that maybe the Windows Update service was going nuts. Nothing changed. Whenever Windows was running in Parallels, the Task Manager showed the disk activity pegged at 100%.

I tried all sorts of online solutions, but none worked. On a whim, I…