A TypeScript question

This is posted under JS since there is no TypeScript category. One must confess to breaking one of my very few cardinal rules… Start at the beginning. Seems I’ve started at the end, with Advanced Objects.

car_class_linter_errors_typescript

My question is, what is the linter trying to tell me, here? I’ve made it to step 14 of this project.

https://www.codecademy.com/courses/learn-typescript/projects/self-driving-car

I know, I should have started at the beginning but this all got going when a question arose in the forums a few days ago and I jumped into the lesson, only to be told I needed to go to the beginning, which I did. It was only later that I discovered this is at the end of the track. If I’m in too deep of water, don’t be afraid to chastise me for breaking my own rule.

It looks like you just need to define the respond() method like you normally would:

I think this is because the interface is describing the shape of the class, and here it is describing respond as a function that returns the void type, rather than as a property.

I think this would have been a little less confusing though if the exercise hadn’t given the hint to write it in this format:

image

If you check out the TypeScript documentation on implementing interfaces, they show that you can describe methods in the interface using the syntax below:

To me, this makes it clearer (as someone new to TypeScript) how the corresponding method should be defined in the class definition.

That said, I also broke your cardinal rule and haven’t taken the rest of the course where it may or may not explain this more clearly.

2 Likes

Thanks for looking in, @el_cocodrilo. I gave the documentation a quick read and changed the interface to read,

import { getObstacleEvents } from './computer-vision';

interface Events {
  [eventType: string] : boolean;
}
interface  AutonomousCar {
  isRunning?: boolean;
  respond (events: Events): {};
}
interface AutonomousCarProps {
  isRunning?: boolean;
}
class Car implements AutonomousCar {
  isRunning; 
  constructor(props: AutonomousCarProps) {
    this.isRunning = props.isRunning;
  }
  respond: (events: Events) => {
    if (! this.isRunning) { 
      return console.log(`The car is off.`);
    }
  }
}

const autonomousCar = new Car({isRunning: true});
console.log(autonomousCar.isRunning)

However, the linter is still squawking over a number of minor details. See below image…

cal_class_still_with_linter_warnings

It seems not to like the NOT being before this, which is obviously going to mean more reading to find a compromise.

Changed to if signature to rid it of all the linter warnings, but three errors remain.

car_class_step15_still_errors

$ tsc
index.ts:20:5 - error TS1131: Property or signature expected.

20     {
       ~

index.ts:23:3 - error TS1128: Declaration or statement expected.

23   }
     ~

index.ts:24:1 - error TS1128: Declaration or statement expected.

24 }
   ~


Found 3 errors.

$ 

Off to read some more. BTW, I did go to the beginning of the track and am making my way forward. Hopefully I can get a better grip of the situation, but any advice you may still be able to give will be welcome.

@mtf,

You’re still defining respond() in your class like this:

respond: (events: Events) => {
    if (! this.isRunning) { 
      return console.log(`The car is off.`);
    }
  }

…which is what I think is causing the problem.

If you look at my screenshots above, in both of them I changed it to be

respond(events: Events) {
  if (!this.isRunning) {
    return console.log('The car is off.');
  }
}

…inside of the class definition, and that seemed to fix the problem.

I’ve never used the methodName: () => {} syntax to define a method before, so I’m not sure if that’s normal in JavaScript. However, it seems that in TypeScript you only use that syntax inside of interfaces and not classes.

interface  AutonomousCar {
  isRunning?: boolean;
  respond (events: Events): {};
}
interface AutonomousCarProps {
  isRunning?: boolean;
}
class Car implements AutonomousCar {
  isRunning; 
  constructor(props: AutonomousCarProps) {
    this.isRunning = props.isRunning;
  }
  respond (events: Events) {
    if (!this.isRunning) {
      return console.log('The car is off.');
    }
  }
}
$ tsc
index.ts:19:3 - error TS2416: Property 'respond' in type 'Car' is not assignable to the same property in base type 'AutonomousCar'.
  Type '(events: Events) => void' is not assignable to type '(events: Events) => {}'.
    Type 'void' is not assignable to type '{}'.

19   respond (events: Events) {
     ~~~~~~~


Found 1 error.

$ 

:man_shrugging:

1 Like

So we’re back to the interface using void. Let me see what happens.

D’oh! Just needed to read step 17. blush

Thanks a tonne!

1 Like

No problem. It was definitely a confusing one!

1 Like

FTR, made it through to the final step.

$ tsc
$ node index.js
Executing: turn right
Executing: turn left
$ tsc
$ node index.js
The car is off.
$ 

The latter case has isRunning:false.

Wish me luck on the next level.

1 Like

My understanding is that using arrow function notation when defining methods is a very bad idea because it messes with how JS uses the ‘this’ keyword. In a “normal” programming language, ‘this’ always refers to the object in which it is called, but for some reason in JS it is undefined unless you specifically bind it in the parent scope in which it’s being called. Arrow functions cannot be bound to a ‘this’. I’ll be honest, I still don’t understand how JS uses ‘this’, and I think my last 2 sentences may not be entirely correct, but either way it shows why avoiding arrow functions is a good idea for object methods. (I’m a java guy, so the way JS uses ‘this’ makes my brain explode)

At the end of the day, the arrow function is essentially a lambda function, which are supposed to be used in cases where a function is only going to be used once. Thus there is no point in giving it a name, because it will never be referenced again. So using an arrow function in a method definition sort of breaks that convention as well. I mean. to be fair, that’s only a convention in other languages so JS doesn’t have tofollow it, but I think it helps make sense of why you should avoid defining an object method with it.

1 Like

Void type means that nothing is returned, but defining respond as:

respond (events: Events): {};

tells tsc to expect an empty object literal to be returned from the function. But the actual definition you used in AutonomousCar returns nothing. So tsc is objecting that the class and interface have different return types. Technically, no return statement is even needed.

return console.log('The car is off.');

console.log() has no return value, so returning it is the same as returning nothing, which means it isn’t even necessary to say it. I guess it can be used to force the function to end if you want it to after that condition is checked, but it would be more clear to write as follows:

respond (events: Events) {
    if (!this.isRunning) {
      console.log('The car is off.');
      return;
    }
  }

ps. I know you finished the project before I wrote my replies, so I guess they are more for future readers’ benefit.

2 Likes

Hence why I rarely use arrow functions in JS. Same thing with this.
JavaScript is not the language I use most of the time, so when I do I tend to stick to the stuff I know will work. I pretty much only use this when I can tell it is going to work the way this works in Dart or self works in Python.

I also suspected this was the case, so I’m glad you chimed in on this one because I forgot to look into it. :100:

1 Like