How to Add Angular Component Input Validation

AngularDeveloper97
Netanel Basal
Published in
5 min readSep 25, 2018

--

16 August 2023 Update

I wrote this article when angular didn’t support this, as of angular 16 this is supported by angular natively. See Angular16 release post here for more information. Having said that you can still read this article if you’re interested in how I arrived to the solution, as that is in my opinion far more interesting than the final solution itself.

This article is a follow up on an article I have written a while back describing how one would validate an angular components inputs, if you haven’t read it yet make sure you check it out here!

At the end of the previous article, I have shown how to use the power of typescript decorators to validate the presence of a given component’s input.

What we want to cover this time is how we would validate the data type as well, this is inspired by the popular react prop-types library.

If we look at the isRequired decorator as it stands right now, it looks like this:

And we would use it like this:

What we want to do, is to have the isRequired decorator take in a parameter representing the type we expect. To achieve this, we need a decorator factory. The typescript documentation describes it as followed:

If we want to customize how a decorator is applied to a declaration, we can write a decorator factory. A Decorator Factory is simply a function that returns the expression that will be called by the decorator at runtime.

This might sound cryptic to some, but don’t worry! We will just be wrapping our existing function in a function that returns it.

Transforming a regular decorator into a decorator factory

Now we have a so-called decorator factory; we can use it to pass in arguments into our decorator function. We will use it to pass in the data type we are expecting; our decorator factory will end up looking like this:

The interesting parts are line 3 and line 19 to line 24.

1- We created a requiredType parameter that will be provided by the consumers of our decorator function.

2- In the case an input had a required type, we check if the provided value after initialization met the components required type which is decided by the creator of the component like so.

You can see how a live example in the following stackblitz:

Like expected, a console.error is called

There is only one minor problem with our implementation. Considering the type checking code is only executed in the ngOnInit lifecycle hook, it will just run once. So if the input value changes to a type our component doesn’t support, the developer will be not notified of a said violation. While it’s quite unlikely this occurs, this is a learning opportunity!

Instead of putting all of the functionality in one big decorator, we’ll create a new decorator named isTypeof that will check the type of an input whenever a new value is set.

Some of you may remember using the getters & setters technique to tackle this problem from the previous article, but I was quick to dismiss using it as we would need to write getters & setters for every member we want to validate.

This is inefficient, but what if… Could we combine the power of getters & setters and decorators? with the power of Object.defineProperty we can! We can use it to create getters and setters in our decorator dynamically.

Our decorator factory function looks a bit like the isRequired decorator in terms of its signature. Passing in an argument is mandatory now as its this decorators responsibility to check the data type, after that we are defining a property on the targets prototype. In simple words, we are overwriting the property’s implementation with ours so we can react to value changes.

Line 15 to 22 contain our type checking code which will be executed whenever the value of the member we are decorating is set. The final result looks a bit like this, change the values back and forth and check the console.

We can take this one step further. Instead of simply checking if the provided value matches a primitive type, we can write a re-usable decorator that checks if the supplied value is an instance of an object, falls within a specific range or matches a select of values. The possibilities are endless! Our previous decorator is a great starting point for our new re-usable decorator, lets’ get rid of the type checking code.

What we want to do now is to have this decorator do a specific check whenever a value is set.

We’ll do so by having it take a function as a parameter that will be called whenever a value is set. This new function needs to have information about the target, property and the new value to present an informative error.

Next, we need to figure out a way to pass in a function as an argument to our decorator. This will look a bit like this:

Considering isBiggerThan is a function call, its return value will be passed to the validateAgainst decorator. So we want to ensure the isBiggerThan function returns a function, functionception!

You can see the new decorator and validator function in action. I rewrote our old type checking decorator to a validator function and used our newly created isBiggerThan validator function to check if values passed to the age input exceed 20, if they don’t an error will be logged.

One last improvement!

While all of these checks will help us a lot during development, it makes no sense to run any of this code in production. Luckily angular provides us with a isDevMode function, which will return false if production mode is enabled. In the case of angular/cli builds this will be the case if you build using the --prod flag, if you use another build system, you’ll need to call enableProdMode as part of your production build.

How would we go on using the isDevMode? we’ll simply add it to our validateAgainst decorator, if the function returns true we will exit our function early.

That's it! I would like to add that although both my articles were focussed on angular inputs as having runtime checking on those has proven to be useful in my day to day life, these decorators and validation can be applied on any class property members. Furthermore, they can be used in any of your typescript projects if you exclude the angular devMode part.

--

--