Stencil Tricks
Published in

Stencil Tricks

How to build an image uploader component with StencilJs

Allow me to help you to build an image uploader component with StencilJS.

I was reading this awesome tutorial by Cathy Ha about how to build an image uploader component with Vue.js. And I asked myself: How to build such a thing with StencilJS?

As Cathy already stated: Coding with a relatively new tools can be challenging due to the lack of (good) tutorials. Especially when you have a specific thing like an image uploader in mind.

So, that’s why I’ve decided to build my own image uploader component with StencilJS and share my experiences and to write this tutorial to help you understand the possibilities of StencilJS.

Check out this git repo for the source.

Things that we’ll implement

We’re going to implement the following:

  • The component is limited to handle only one file at the time;
  • The component checks for a size limit of 1mb and it must be an image;
  • We should be able to see an image preview of image;
  • The uploaded image needs to be exposed to other components in Blob format;

Step 1: Setup

Before we can start building our component we have to setup a development environment, which is very easy with the starter repository, provided by our friends at StencilJs.

Note: You need npm v6 or higher!

# initiate a new StencilJs component named: image-uploader
git clone https://github.com/ionic-team/stencil-component-starter.git image-uploader
# goto the component's folder
cd image-uploader
# remove the remote url
git remote rm origin
# install dependencies
npm install
# start the live reload server
npm start

The browser will open automatically and you should see the following:

Cool! All is there to build our awesome image uploader component. The src folder structure should look like this:

└── src
├── components
| └── my-component
| ├── my-component.css
| ├── my-component.e2e.ts
| └── my-component.tsx
├── utils
| ├── utils.spec.ts
| └── utils.ts
├── components.d.ts
└── index.html

Step 2: Create the template

The first step is to replace the ‘hello world’ render method in my-component.tsx by StencilJs with our own template, which looks like this:

The template consists out of the following two parts:

  1. An image upload button, which provides the user with the functionality to select an image and upload it automatically. Notice the `onChange` function, which will fire the upload mechanism, after the user selected an image;
  2. An uploaded image preview container where the uploaded image will be previewed.

Warning: If you still have the application running, it probably throws numerous errors. This is because we didn’t implement logic yet and there’s still some StencilJS boilerplate logic out there. But, don’t worry, we’ll fix it asap ;-)

Step 3: Make things look more compelling

Next is to add some styling to make it look a bit more compelling. Add the following css to my-component.css:

This how it looks like after styling:

Step 3: Add logic

Now it’s time to add the actual logic to the component. To refresh our minds, here are the “business” requirements again:

  • The component is limited to handle only one file at the time;
  • The component checks for a size limit of 1mb and it must be an image;
  • We should be able to see an image preview of image;
  • The uploaded image needs to be exposed to other components in Blob format;

Upload image handler
First we need to handle the upload event, triggered by the upload button and check if the user actually uploaded a single image. If not, we’re going to throw an error message in the console.

Add a new method, called onInputChange() to the MyComponent class in my-component.tsx. The component’s code will look like this:

  1. onInputChange() accepts an FileList object that contains all the files to be uploaded simultaneously by the user. It can contain multiple images, so we need to check if the number of images uploaded is equal to 1. If not, an error message will be thrown in the console.

Check image size & type
The next step is to check if the image size doesn’t exceed the 1mb limit and if the uploaded file is actually an image.

We need to add the following properties to the component:

const MAX_UPLOAD_SIZE;
const ALLOWED_FILE_TYPES;

And the following methods:

private checkFileSize(size: number): boolean {}
private checkFileType(type: string): boolean {}

The code will look like this:

  1. The MAX_UPLOAD_SIZE property defines the maximum file size of the image in bytes. In our case only files smaller then or equal to 1mb (or 1024 bytes) are allowed;
  2. The ALLOWED_FILE_TYPES property contains a regular expression which defines the accepted file types. In our case images of any type are allowed;
  3. The checkFileSize() method accepts a number and returns a boolean. True if the file meets the criteria, false if not;
  4. The checkFileType() method accepts a string and returns a boolean. True if the file meets the criteria, false if not;
  5. In the onInputChange() method we’ll handle the checks implemented.

Upload image with HTML5 FileReader Api
The next step is actually uploading the image. We’re going to use the HTML5 FileReader Api for that. You can read more about this on MDN.

First we need to create a method called uploadImage(). Here we’re going to handle the file uploading process. The FileReader Api provides us with a set of helpers which we can implement to know what’s going on before, during and after the upload. We need to implement the following steps:

  1. Create a new instance of the HtmlReader api;
  2. Monitor if the upload has started with onloadstart();
  3. Do the actual uploading with onload();
  4. Check if the uploading is finished (either in success or failure) with onloadend();
  5. Trigger an event when the upload has failed with onerror();
  6. Finally read the contents of the uploaded file and return a data: url, which contains the file’s data.

The code will look like this:

Image preview
After the upload is successful we need to display a preview of the uploaded image. With the uploading method in place, we are actually able to do so. We just need to:

  1. Access the image preview DOM element;
  2. Extend the onload() method.

The code will look like this:

  1. The elementHost property is decorated with a StencilJS Element Decorator. This is how to get access to the host element within the class instance. This returns an instance of an HTMLElement, so standard DOM methods/events can be used here;
  2. In theonLoad() method the elementHost property is used to access the DOM element. Note that we first need to access the component’s shadowRoot in order to get access to the container. By default, StencilJS components markup structure, style, and behavior are hidden and separated from other code on the page, so that different parts do not clash, and the code can be kept nice and clean by encapsulating it in its own shadowDOM. Learn more about it here;
  3. Then we set the data: url as background image of the element.

Emitting events

The final step is to expose the uploaded image data to the rest of the application and we’re going to use StencilJS Event Decorator for that.

Add a decorated property onUploadCompleted to the class and extend the onLoad() method with the actual emitting of the data.

The final code will look like this:

  1. Components can emit data and events using the StencilJS Event Emitter decorator. It dispatches a CustomEvent for other components to handle. In our case the event dispatches the uploaded image as Blob.
  2. After a successful upload the image blob data will be emitted to the outside world. The event’s name is: onUploadCompleted. This name can be used to capture data emitted by this event.

Test the component

Open index.html and:

  1. Add a reference to our component;
  2. Add an eventListener that listens to the onUploadCompleted event.

The code will look like this:

Just fire up the application (if you haven’t already) with npm start and start uploading!

That’s it! There’s much room to improve this component, like passing props to resize the image, handling multiple files or connect it to a backend. If you see any points for improvement or spot a mistake in my code, please please leave a comment!

Feel free to check out the code in this git repository.

About the author

☞ If you liked this post you can buy me a coffee.

Stefan reads every response on Medium or reply on Twitter, so don’t hesitate to let him know what you think.

☞ To hear from the author in the future, check out his gists or follow him on Twitter.

Please tap or click “♥︎” to help to promote this piece to others.

--

--

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
Stefan Nieuwenhuis

Stefan Nieuwenhuis

Stefan Nieuwenhuis is software engineer, loves to play sports, read books and occasionally jump out of planes (with a parachute that is).