A deep dive on Angular decorators
- Method decorators are very similar to property decorators but are used for methods instead.
- A method decorator will be called with the value of the method it’s decorating, and a class decorator will be called with the class to be decorated.
- Back to Angular, the internal codebase uses decorators extensively and in this post we’re going to look at the different types of decorators, the code they compile to and how they work.
- Parameter decorators allow us to decorate parameters in our class constructors.
- Let’s change our code above to execute the Console function with a value to match how we use the Angular decorators.
Decorators are a core concept when developing with Angular 2 and above. There’s also an official TC39 proposal, currently at Stage-2, so expect decorators to…
Back to Angular, the internal codebase uses decorators extensively and in this post we’re going to look at the different types of decorators, the code they compile to and how they work.
When I was first introduced to TypeScript and decorators, I wondered why we needed them at all, but once you dig a little deeper you can understand the benefits to creating decorators (not only for use in Angular).
method. So why has Angular 2+ chose to use them? Let’s explore.
Table of contents
Before we look at creating a custom decorator and why/how Angular uses them, let’s look at the different types of decorators that Angular offers. There are four main types:
Each decorator has a unique role, let’s jump to some examples to expand on the list above.
Angular offers us a few class decorators. These are the top-level decorators that we use to express intent for classes. They allow us to tell Angular that a particular class is a component, or module, for example. And the decorator allows us to define this intent without having to actually put any code inside the class.
decorator example with classes:
Notice how both classes by themselves are effectively the same. No code is needed within the class to tell Angular that it is a component or a module. All we need to do is decorate it, and Angular will do the rest.
These are probably the second most common decorators that you’ll come across. They allow us to decorate specific properties within our classes – an extremely powerful mechanism.
. Imagine that we have a property within our class that we want to be an input binding.
Without decorators, we’d have to define this property in our class anyway for TypeScript to know about it, and then somewhere else tell Angular that we’ve got a property that we want to be an input.
decorator above the property – which Angular’s compiler will automatically create an input binding from the property name and link them.
We’d then pass the input binding via a component property binding:
within the new component method:
which is decorated, which is easier to change, maintain and track as our codebase grows.
. This allows us to tell Angular that when an event on our host happens, we want the decorated method to be called with the event.
Parameter decorators are quite interesting. You may have come across these when injecting primitives into a constructor, where you need to manually tell Angular to inject a particular provider.
that lets us tell Angular what we want that parameter to be initiated with:
Due to the metadata that TypeScript exposes for us we don’t actually have to do this for our providers. We can just allow TypeScript and Angular to do the hard work for us by specifying the provider to be injected as the parameter type:
Now that we’ve covered the types of decorators we can use, let’s dig into what they actually are doing – and why we need them.
Creating a decorator
It makes things a lot easier if we understand what a decorator is actually doing before we look into how Angular uses them under the hood. To do this, we can create a quick example decorator.
Decorators are actually just functions, it’s as simple as that, and are called with whatever they are decorating. A method decorator will be called with the value of the method it’s decorating, and a class decorator will be called with the class to be decorated.
Let’s quickly make a decorator that we can use on a class to demonstrate this a little further. This decorator is just going to simply log the class to the console:
. The target will in fact be the class that we decorate, which means we can now decorate any class with our decorator and see it outputted in the console:
Want to see it in action? Check out the live demo.
Passing data to a decorator
When we use the decorators in Angular we pass in some form of configuration, specific to the decorator.
) to be passed through to the decorated method.
function with a value to match how we use the Angular decorators.
) and also the class that it’s applied to:
You can see the changes here.
This is the basis for how the decorators in Angular work. They first of all take a configuration value and then receive the class/method/property to apply the decoration to. Now that we have a brief understanding of what a decorator actually does, we’re going to walk through how Angular creates and uses it’s own decorators.
. Angular does this by using a factory for each type of decorator.
We’re not going to dive into the actual code that Angular uses to create these decorators because we only need to understand them on a higher level.
The whole point of a decorator is to store metadata about a class, method or property as we’ve already explored. When you configure a component for example, you’re providing metadata for that class that tells Angular that we have a component, and that component has a specific configuration.
Each decorator has a base configuration that you can provide for it, with some defaults applied for you. When the decorator is created using the relevant factory, the default configuration is passed through. For instance, let’s take a look at the possible configuration that you can use when creating a component:
. This is specified when the decorator is created so we don’t need to add it whenever we create a component. You may have applied this line of code to modify the change strategy:
An annotation instance is created when you use a decorator. This merges the default configuration for that decorator (for instance the object you see above) with the configuration that you have specified, for example:
Would create an annotation instance with the properties of:
Once this annotation instance has been created it is then stored so Angular can access it.
If a decorator is used on a class for the first time, it creates a new array and pushes the annotation instance into it. If this isn’t the first decorator that has been used on the class, it pushes it into the existing annotation array. This allows decorators to be chained together and all stored in one place.
For example, in Angular you could do this for a property inside a class:
) to store these annotations, using the class as an array. This means that it can then later on fetch all of the annotations for a specific class just by being pointed to the class.
How decorators are applied
So we know now how and why Angular uses decorators, but how are they actually applied to a class?
Take a standard, ES6 class –
TypeScript will then convert this over to a function for us:
Now, if we decorate our class, we can see where the decorators are then actually applied.
TypeScript then outputs:
This gives us some actual context as to how our decorators are applied.
as the argument).
Demystifying decorators is one step into understanding some more of the Angular “magic” and how Angular uses them. They give Angular the ability to store metadata for classes and streamline our workflow simultaneously.