Getting to Know Stencil Decorators

Gil Fink
5 min readSep 21, 2017

--

Stencil Logo

In the previous post I wrote about how to get started with Stencil and showed a simple example of a collapsible panel component. In today’s post I’m going to explain the decorator options that you can use in Stencil.

Decorators

Before I delve into Stencil decorators, let’s first understand what are decorators. Decorators are functions that annotate and modify classes and properties at design time. You can just look at them as a hook to add functionality during design time. Frameworks, such as Angular, use decorators to help their compiler understand how to add relevant framework functionality (for example dependency injection or animation) to the decorated class/property. Currently, decorators are a ECMAScript stage 2 draft but they can be used with TypeScript language.

There are 4 types of decorators in TypeScript:

  • Class
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
  • Property
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
  • Method
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
  • Parameter
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

Each decorator receive the decorated target as first argument and different arguments that enable us to add our functionality. We use the created decorators with the @ character, so if we created a Component decorator we will use it as:

@Component()

Stencil uses decorators to annotate the component and to explain Stencil compiler how to wire the component we are creating.

Stencil Decorators

In @stencil/core you can find the following decorators:

export declare const Component: ComponentDecorator;
export declare const Element: ElementDecorator;
export declare const Event: EventDecorator;
export declare const Listen: ListenDecorator;
export declare const Method: MethodDecorator;
export declare const Prop: PropDecorator;
export declare const PropWillChange: PropChangeDecorator;
export declare const PropDidChange: PropChangeDecorator;
export declare const State: StateDecorator;

Every decorator has a different purpose and most of the time you won’t use all of them in the component you build. On the other hand, it’s good to know all of them since you might need them while developing your component and the list is currently short.

Component

The main decorator you are going to use is the Component decorator. The Component decorator configures your component and includes one mandatory property which is the component tag. You should follow Custom Elements restriction while naming your tag — a component should include a dash in it’s name. If you will forget this restriction don’t worry because Stencil compiler will shout at you. The Component decorator can also get the following optional properties:

styleUrl?: string;
styleUrls?: string[] | ModeStyles;
styles?: string;
shadow?: boolean;
host?: HostMeta;
assetsDir?: string;
assetsDirs?: string[];

The first three properties configure the style url/s or you can pass a style string. The shadow property configure whether to add shadow DOM to wrap the component HTML or not. AssetsDir/s configure where to find component assets such as images. Regarding the host property, I didn’t see anything about it in the documentation and I guess that it’s an option to configure the host element somehow.

A Component decorator usage example:

import { Component } from '@stencil/core';@Component({
tag: 'st-toaster',
styleUrl: 'toaster.scss',
shadow: true
})
export class Toaster {
...
}

Element

The Element decorator enables you to get a reference to the component’s host element. From time to time you will want to manipulate the host during runtime, so it’s really helpful to have that as part of your instance.

If you are coming from Angular background it’s really resemble the ElementRef injectable.

An Element decorator usage example:

import { Component, Element } from '@stencil/core';@Component({
...
})
export class Toaster {
@Element() toasterDiv: HTMLElement;

showToast() {
this.toasterDiv.style.display = 'block';
};
}

Event and Listen Decorators

The Event decorator enables you to expose an EventEmitter which later can be used to dispatch custom component events. The Listen decorator enables you to listen to those custom events in the containing components. For example, you can expose a toasterFadeOut event to indicate that a toast message ended:

import { Event, EventEmitter } from '@stencil/core';@Component({
...
})
export class Toaster {
@Event() toasterFadeOut: EventEmitter;
toasterFadeOutHandler() {
this.toasterFadeOut.emit();
}
}

A component that consume the event might look like:

import { Listen } from '@stencil/core';export class ToasterApp {
@Listen('toasterFadeOut')
toasterFadeOutHandler(event: CustomEvent) {
console.log('Received event: ', event.detail);
}
}

Pay attention that ToasterApp should contain the emitting component or else nothing will happen.

Listen can also accept an application wide events such as scroll or keyboard events. You can take a look at this option in Stencil website.

Method

The Method decorator is used to indicate a component exposed API method. If you want to expose some functionality from the component, this is the way to do that:

import { Component, Element } from '@stencil/core';@Component({
...
})
export class Toaster {
@Element() toasterDiv: HTMLElement;
@Method()
showToast(notification) {
this.toasterDiv.textContent = notification;
this.toasterDiv.style.display = 'block';
};
}

Prop

The Prop decorator is used to indicate that a member is exposed as component attribute. If the component need some data in order to work this is were you will declare a Prop and expose it. The Prop decorator currently receives three optional configuration properties:

context?: string;
connect?: string;
mutable?: boolean;

There is no documentation about these three configuration properties so I’ll add the explanation to what they do in the future.

You saw me use the Prop decorator in my previous post but let’s see it again:

import {Component, Prop, State} from '@stencil/core';

@Component({
tag: 'collapsible-panel',
styleUrl: 'collapsible-panel.css'
})
export class CollapsiblePanel {
@Prop() title: string;
...
}

PropWillChange and PropDidChange

The PropWillChange and PropDidChange are used to wire handlers before a property change and after property changed. These hooks are very helpful if you want to do things before and after a property change. For example, you can add validation before a change and emit some event after a property change occurred. Both of the decorators receive the name of the Prop they should hook to.

The following example shows how to decorate the title Prop:

import { Prop, PropDidChange, PropWillChange } from '@stencil/core';

export class CollapsiblePanel {
@Prop() title: string;

@PropWillChange('title')
willChangeHandler(newValue: boolean) {
console.log('The old title is:', this.title, 'The new title is: ', newValue);
}
@PropDidChange('title')
didChangeHandler(newValue: boolean) {
console.log('The new title is: ', newValue);
}
}

State

The last decorator is State which is used to indicate that a member is part of the component state. Any change to a member that is decorated as State will trigger the component render function.

You saw me use the Prop decorator in my previous post but let’s see it again:

import {Component, Prop, State} from '@stencil/core';

@Component({
tag: 'collapsible-panel',
styleUrl: 'collapsible-panel.css'
})
export class CollapsiblePanel {
@Prop() title: string;
@State() collapsed: boolean;
...
}

Our Stencil decorators journey is now over :)

Summary

Stencil includes a bunch of decorator that you can use in order to explain the compiler how to compile and create your component. In the post I showed you the list of decorators and how to use them.

--

--

Gil Fink

Hardcore web developer, @sparXys CEO, Google Web Technologies GDE, Pro SPA Development co-author, husband, dad and a geek.