StencilJS: creating custom decorators

Michał Kaliszewski
Stencil Tricks
Published in
5 min readJun 13, 2019

Intro…

Decorator pattern is pretty cool: lets you to add some behavior dynamically to instance without changing the class of that instance. Personally I like decorators a lot, especially in form of annotations. At some point I guess I got into them maniacally… I can tell you, enclosing every three lines of code that look generic to custom decorator may not be the best idea. But if you do it smart like Spring or Django gurus you may be fine.

Joining StencilJs world for decorator’s enthusiast will definitely not be a disappointment. Every stenciler is using them a lot, you can check out the list of OOTB decorators here at Stencil docs page.

Alright then, we have a few decorators at Stencil, why creating a new custom is a good idea? Why not a normal util function if I need to put somewhere the code that I see as a utility? Well I’m not saying no, I just find decorator as a bit more elegant and precise so if I can then why not?

How do I create my Stencil decorator?

In short: by hijacking. Sounds badass, isn’t it? Example will make it less wicked, more clear tough. Suppose you have your web component, say a flyout which you would like to close when user clicks outside of flyout area…

Take a look at mysterious line 12 and forget about it for a moment. How would you close a flyout on click outside? Probably by putting some code to componentWillLoad:

Line 4–13 can of course be a util function. In this case we would call it directly in line 4. Other solution is putting the logic into a decorator, say @ClickOutside() and annotating close() method… we get elegant line 12 from previous gist.

How to implement it? Pretty simple.

First, you need to know how to do decorators in typescript in general (if you’re not that familiar with ts, you check here).

Second, in decorator you need to access some point, say a hook or function, which you know will be called during your component life and yes, hijack it!

Hijacking render()

So we already know we need a function which is called by compiler during component life. You can find such function in my-flyout. Yes, its a render() function. Let’s change a bit default implementation of render():

What we just did in those few lines of our decorator? We basically took original render() function from our component instance and added a few lines of our code to it. From now on each render function execution will call our few lines apart from default render() implementation

In case of our example: proto is MyFlyout class, metodName is close. Lines 18–20 → here goes your decorating code. We are actually decorating a render() method of MyFlyout.

I would probably have two questions if I read that first time: Can you show me registerClickOutside? render() method can be called multiple times during component life, how can I achieve one time execution of my decorator code?

We can add our decorator registration flag, set it during registerClickOutside execution and check it before function real code. And we have it, as simple as that:)

Hijacking lifecycle methods

Is there any simpler solution maybe? Do I always need to mess up with render()? Well, yes there is. As you know Stencil offers hooks in form of lifecycle methods:

You can decorate whichever you want in the similar way as was shown. Some of those hooks are called only once during component life so no need of introducing flag. Let’s see how our example would look like with hijacking componentDidLoad instead of render:

Optimization… optimization everywhere

Why then I’m showing decorating render() first with some hacky flag? Because there is one important drawback with using lifecycle hooks: your component (or at least one component from your bundle) needs to implement the lifecycle method which you’re decorating. This is because Stencil compiler is optimizing a lot! You cannot just add that method from decorator perspective manually to proto and expect that compiler will call it since during compilation (before your decorator code) compiler evaluates if WillLoad, DidLoad and other methods are declared and then calls them in runtime based on this evaluated information. If you want to know how compiler is evaluating check out Stencil sources:

and here…

Shortly speaking: if I’m hijacking componentWillLoad in my decorator then I’m assuming that someone who will use that decorator in his Stencil project would need to have at least one component with componentWillLoad function declared explicitly. Otherwise compiler will optimize and will not call that method at all and thus our decorator code as well will not be called.

Ok, why then render() is better for hijacking? Well its not better: you can have component without render() and again compiler will optimize. But it’s definitely safer to assume that someone who is using your decorator will have at least one component in project with render function defined. Personally, I have never even done component without render()…

At last: you can check for existence of lifecycle method (or render)on proto in decorator and throw a warning message if method is not there. Example of such code you can find here:

We are still forcing to declare lifecycle method but we are also informing about it in a nice way. Same handling should be applied to decorating render.

See also

If you’re still reading it means I can now easily recommend some npm packages — Stencil custom decorators. Check out github repos, they are really compact, one source file in most cases:)

  1. @Lazy Decorator that allows you to call component method as the user scrolls component into the viewport.

(check also version made before Stencil One with web components that use @Lazy decorator: https://www.npmjs.com/package/st-lazy)

2. @ClickOutside Decorator that allows you to call component method as the user clicks outside of the host component

3. @ConstructibleStyle Decorator for Stencil components that gives you the ability to add dynamic styles as constructible stylesheets, with a fallback for non-supported browsers

--

--