The Problem with 'this' in TypeScript (and how to fix it)
This guide walks through the issues with 'this' in TypeScript, along with examining how we can leverage arrow functions to fix scoping problems.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

On this section of asynchronous development, I want to start off by taking a deep dive into how to work with the this construct in typescript because a number of areas that you'll run into will be based on either not understanding how to work with this or possibly just having some confusing errors because of it.

So let's dive into that and I'm going to create a familiar class for us an invoice class and inside of it we're going to have an attribute called Total which will be a number I’m going to make the class very basic so we can drill down on this and also talk about some ways this can become really confusing. So I'm going to create a constructor and in this constructor, I'm going to pass in our total value. And here we're going to set this total equal to total.

class Invoice {
  total : number;

  constructor(total : number) {
    this.total = total;
  }
}

And so right here what we're doing is we're saying this total which is referencing this one up here the total that will be instantiated when the when the invoice is created We're setting it equal to the value of the argument of when it was created. So this is just saying that we want to have the ability to reference this throughout the program. So this is just a very basic class and it's only going to have one method called print total. It’s not going to take any arguments and inside of it it's going to console log out this .total. Now if we come down here and type in var invoice equals new invoice and I'm going to pass in value for 400 for the total and now I want to do invoice .print total and this should print out just 400 so I go to the terminal type tsc and node 027_this. And there you go.

class Invoice {
  total : number;

  constructor(total : number) {
    this.total = total;
  }

  printTotal() {
    console.log(this.total);
  }
}

var invoice = new Invoice(400);
invoice.printTotal();

So that worked. We've gone through this a bunch of times so I would be very concerned if that one didn't work. But now let's come back up and I want to talk about how we could mess up this because right here I think this makes sense where we're saying that this total is going to be referencing this total up here. That part is clear. However one issue that we're going to run into is what happens if we create a nested function. So I'm going to create a function here called print later and print later is going to take one attribute called time, which is going to be when we want it to print and that's going to be of type number. And inside of this, I'm going to call a built-in JavaScript method called set timeout. And if you've never heard of set timeout before that timeout does is it lets us pause the program from running. So we're set timeout. We can say I don't want you to execute this line of code until you know five seconds or 1 second. For this, we're simply going to you know pass one second in but Ill give us what it will do is help give us a nice delay. And more importantly than anything else. It also gives us the ability to embed a function. So set timeout can take a function and this function is going to also, in fact, I'm just going to copy this code right here. So I'm going to copy this right here. And then also set timeout is a function and It takes two arguments the first is a function the second is how long we want it to take. We're going to say we want it to take one second and this is in milliseconds.

  printLater(time : number) {
    setTimeout(function() {
      console.log(this.total);
    }, time);
  }

invoice.printLater(1000);

So now if we come down here and I use the print later command our argument we need with this we need to pass in 1000 milliseconds and now we'll pause it for one second. So as you could tell I copied and pasted this code. So theoretically this total we already saw how inprint total is equal to 400 which was our value right here. Let's see what it does when we run this. Now running the code you can see it prints out for 400 and then it prints out undefined. So that's a little bit confusing considering I literally copied and pasted the code. And this was all contained inside of the same class. The issue is how this works when it's nested inside of another function. So here we have in our print total function. This is not nested inside of multiple functions it's simply inside a print total, which is inside of the invoice class. And what this does for us is it gives the ability to have a direct scope for this.

So, in this case, this is going to always reference the value for the class. However, you're going to run into problems with this when you start nesting inside of them multiple functions because the way JavaScript works with this is it makes the this scope available inside of each one of the functions are print later function. Say that we took out this set timeout in this other function this total would've worked perfectly just like it did right here. However inside of print later we have set timeout. And this other function here. And because of that what we're doing is we are essentially telling JavaScript to expect a second this. So we're saying hey look for this total because this function contains its own version of this. And so what JavaScript went and it is it said OK I'm going to look for this total inside of this function and when it didn't find it then it just said undefined. So that's kind of a problematic thing as especially if you start building complex applications you'll end up seeing a lot of undefined all over the place even when you think that you're working with an attribute if you're working inside an attribute in a class you think oh I have access to call this any time I want but because of how JavaScript works with the term that it's used for as a higher order functions which we're going to get into in the next guide. But what this is saying is that this function has its own scope for this and that can be a really good thing if executed properly. However, in many contexts, it can be troublesome.

So what I'm going to do is I'm going to copy this commented it out and show you how you can get around this issue and I know in this video I probably said this about a thousand times but that's just because that's what it's named. And it's also a pretty common word to use. So anyway here is our print later function and how we're going to get away with this is if you remember back a number of guides ago we talked about the arrow function well the arrow function is kind of a special function in typescript. Because what it lets you do is it keeps the logical scope for this when you use it. So when you use a function like this one then it's going to create its own scope for this. However, the people who created typescript were nice enough to give us a workaround. So if I just use the parentheses and then I use an arrow function then this program is still going to work perfectly. But the difference is and now this total is going to reference the total from the class and not the total from the function that it's nested in. And let's test this out.

class Invoice {
  total : number;

  constructor(total : number) {
    this.total = total;
  }

  printTotal() {
    console.log(this.total);
  }
  //printLater(time : number) {
  // setTimeout(function() {
  //  console.log(this.total);
  // }, time);

  printLater(time : number) {
    setTimeout(() => {
      console.log(this.total);
    }, time);
  }  
}

var invoice = new Invoice(400);
invoice.printTotal();
invoice.printLater(1000);

So I'm going to run this again and you can see that printed out perfectly. So now when you use an arrow function then you have access to this as it's properly scoped so I hope that this does a couple things one I hope it clarifies a little bit more what this is because it's one of those things that usually have to work through a number of times and see from a number of different angles and examples before it becomes clear on how it works. But secondly, I hope this helps you can overcome and have a plan for when you run into issues where this isn't working because it's nested inside of another function. This is a great workaround and it's a fantastic thing that typescript and that really gave you access to accessing these kinds of values as opposed to pure JavaScript where you'd have to build a lot of workarounds to make this to make this work. This ends up giving you cleaner code and cleaner access to the data. So this is how you work with this in typescript.

Resources