Class Decorators in TypeScript
This guide explains how to work with class decorators in TypeScript, including how to add custom behavior to classes.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this guide, we're going to take a deeper dive into decorators in we're specifically going to look at how to work with class decorators. I wanted to work with class decorators because this is one of the most common ways that you're going to be seeing angular 2 applications so I thought that it would be important to give you a firm understanding of here.

So first thing I'm going to do in order to work with this is I'm going to create a class and this class is going to be called Accounts Payable. It's just going to be a basic class and it's going to be so basic that the constructor is not even and taking arguments and it's not going to have any behavior. Now the next thing that I want to be able to do is from here and from right now I'm not even going to add any other methods or anything either. So in order to create a class decorator, The first thing that we want to do is to actually create a function because remember that functions and decorators are the same thing. Decorators are just functions that take in a specific set of arguments and that kind of deal so for a class decorator. I'm going to create a function detailed log method here and it's got to take one parameter. It's going to be called dashboard in a string. So what kind of vision for a detailed log is it going to be? Is it's a pretty common thing to be able to want to log a behavior or log things that are occurring inside of an application. And what we're doing here is we're creating a logging mechanism that we can layer on top of any class. And what we're going to do is actually create some kind dynamic behavior which means we're going to say if this class that we're attaching it to is a billing class and accounts payable is a billing class then I want you to perform a different set of actions than if it was a regular class.

class AccountsPayable {
  constructor() {}
}

So what I can do with that is all say if and we'll say dashboard equals billing. Then inside of this, I want you to console log. "Working in the billing departments" and seems like something that you'd want to have as you say they have a program and you have some more high-security elements inside of it. You may want to be notified if someone is accessing a part of the application at a specific time or something like that so I can see this being useful. The next thing we need to do because remember that decorator's need to return a function you can't just have a decorator and put a console log statement and it's going to be a decorator. Then you might as well just have a function you call a decorator has to actually return a function.

So what I'm going to do is say return function. And remember that I also said that we have to be a little bit picky and you have to have an understanding of the arguments that get returned. And in this case it's going to be a little bit easier than the introduction example. Whenever we're working with class decorators we just need to return a function that has an argument of target. And in this case we're going to say target object and we're not going to return anything else. And that is that. Now what this is essentially doing is we're saying we need to establish what the target for this function is going to be typescript in the back end is going to handle all of this for us so we don't really have to worry about this too much. But what we're essentially saying is I want you to target the master class of named object. And so this is a pretty high level we're not really getting into the granular classes that that typescript can have. There are times when you will need to get more detailed in this. But when that occurs it's usually going to be in an angular to happen will be pretty clear by that time based on instructions or documentations on which one you're going to do. The important thing to know is that one you have to return a function and two when you're working with a class decorator that has to have a target don't let that kind of intimidate you too much because of that. I know the first time I started to get into decorator's it seemed very confusing and it wasn't until I got into the angular side of it that it started to make sense to make sense.

So this what this is checking is it saying OK this dashboard that we're applying this to, is this a billing dashboard? And if it is then we want you to do all the behavior in here. If not then we just want you to return the function with the target object. So in a real world scenario, you would set up an alert or log to a specific file when a user had access to this billing dashboard. This is more just to show that you can have dynamic behavior. So now that we created this decorator we can come up above the class and this is what the syntax is. Above the class we're going to add an @ symbol and I'm going to say detailed log. And remember that detailed log takes in a argument. In this case it takes in the dashboard. So here I say billing. And that is all I'm going to do. And let's see what accounts payable doesn't like about that. It says experimental support for decorators is a feature subject to change option. So set the experimental decorator's option to remove this warning. Let's see because I'm pretty sure I have that. So this let me close out of this and get back into it. Ok and it's gone. That was just a little glitch with the way sublime does it. That's something that you may or may not have seen if you have been working with sublime sometimes the warning is kind of jumping the gun a little bit. Or you know you just need to get out of it and back in the file to fix it. Everything there looks like it is working.

So let's come up and I want to actually create a post so I'm going to do. Var post and this is going to be equal to a new accounts payable. And we don't have to pass anything into it. And that should be it because we dont have anything besides a constructor. So let me run this. node 031_class_decorator. And you can see that it says working in the billing department. So that's pretty cool.

@detailedLog('billing')
class AccountsPayable {
constructor() {}
}

function detailedLog(dashboard : string) {
  if(dashboard == 'billing') {
    console.log('Working in the billing department');
    return function (target : Object) {};
  } else {
    return function (target : Object) {};
  }
}

var post = new AccountsPayable;

So what we have here is a decorator. That can give us dynamic behavior it has a logging mechanism and based on the class that we have it can pass in certain dynamic things. So let me copy this and let's create another class. So I'm going to create another class and this one is going to say warehouse So this department or this dashboard say warehouse and this can be the product manager class. It doesn't really matter. But what we can do here is to create a new product manager class. Let's see what happens because our logging system should not trigger this console log system unless we pass the billing parameter inside of the decorator so let's run this again.

@detailedLog('billing')
 class AccountsPayable {
   constructor() {}
 }

@detailedLog('warehouse')
 class AccountsPayable {
   constructor() {}
 }

function detailedLog(dashboard : string) {
  if(dashboard == 'billing') {
    console.log('Working in the billing department');
    return function (target : Object) {};
  } else {
    return function (target : Object) {};
  }
}

var post = new AccountsPayable;
var pm = new ProductManager;

As you can see it only printed out working in the billing department so that's good. That's exactly what we wanted to happen. So what is occurring here is in a real-life scenario obviously we'd probably pass more items in. But for this one let's pretend that you just got asked to implement the feature so that in case anyone's working on the billing dashboard the CEO or someone in the company wants to have a record of it and we may have other ones for the warehouse department we may want to see what they're working on or you know get some other information. And so when a decorator allows us to do is to implement all this behavior and then we can share that across all of these other classes so we can layer this on to any class that we want to have a logging mechanism attached to. This makes it possible to have much cleaner code bases. It works the way that. Classes and inheritance work except in this case you can see that product manager in accounts payable that shouldn't inherit from the same class so it is poor coding practice to make these inherit from a class. And so what this does is when we don't have items that have the same where they shouldn't inherit from the same class way decorator's allow us to do is to give shared behavior to specific classes without having to dive into inheritance.

So this is something that is considered a best practice in most modern development. You see this kind code in a lot of different things. In Ruby they have a module called the fordable module that allows you to have this same kind of behavior. Javascript has a lot of things built around the ability to work with decorators and to layer on behavior without having to use inheritance so this is something you'll see quite a bit. But more than seeing it in other languages you will see this all the time in angular 2 development which is why we talked about it here. So that is how you can use class decorators and in the next guide we're going to talk about how we can use function decorators.

Resources