TypeError: apiAirplane.takeOff is not a function.
I may not let virtual airplanes take off every day, nevertheless an error of this kind has been quite familiar to me on other objects.
Especially in Angular development with Typescript and the use of API’s the whole thing dropped in my face a few times. In the meantime I have seen the error so often that it will probably not happen again. Or rather, I know about this one option, which can trigger the error.
At this point I introduce why objects in Typescript or Javascript are sometimes objects of a class, and sometimes just objects without cool functions you may want to use. In the end there can be many reasons why the x is not a function error occurs. But almost always it is due to the fact that where you call the function something is not what you think it is. I will show you an example of why this can happen.
For my explanation, I first need a simple class. For example, an airplane. It has a name, a state and a function with which I can use to change the state:
class Airplane {
public name?: string;
public isFlying = false;
public takeOff(): void {
this.isFlying = true;
}
public toString(): string {
return JSON.stringify(this);
}
}
So I create a new aircraft and have the state output once. Then I call the takeOff function and see if the state of my object has changed.
const airplane: Airplane = new Airplane();
airplane.name = "XCF345";
console.log("Airplane state before takeoff: " + airplane.isFlying);
airplane.takeOff();
console.log("Airplane state after takeoff: " + airplane.isFlying);
Output looks like this:
ts-node object.ts
Airplane state before takeoff: false
Airplane state after takeoff: true
Shouldn’t be that surprising.
Now imagine we get the aircraft object from a web API or backend that gives us the data about the aircraft. In most libraries you use prebuilt code to make HTTP requests. Often this code uses generices, which allows me to specify which type I expect to receive via the interface.
Hit me up if you think it’s a bit weird, but I made a kind of very dull mock of a “web API”. This returns me the data for one single airplane:
function getAirplaneFromAPI<Type>(): Type {
return {
name: "WER875",
isFlying: false,
} as Type;
}
So I do exactly the same stuff I did before with the explicitly created object but now with the plane received via the “API”. To do this, I create an empty airplane object. I tell the API that I expect an aircraft object and then put it into my created variable:
let apiAirplane: Airplane;
apiAirplane = getAirplaneFromAPI<Airplane>();
console.log("Airplane state before takeoff: " + apiAirplane.isFlying);
apiAirplane.takeOff();
console.log("Airplane state after takeoff: " + apiAirplane.isFlying);
Now run it and see what happens:
ts-node object.ts
Airplane state before takeoff: false
/Users/rico/code/Test/object.ts:35
apiAirplane.takeOff();
^
TypeError: apiAirplane.takeOff is not a function
There we have our error! Damn why? When I write the code it suggests to me that this function exists, why doesn’t it work now when I run it?
Well, this is Javascript and stuff doesn’t always contain what it claims to be. And also the use of Typescript does not prevent that a variable does not contain what you think it contains. But that’s a big and different topic and I won’t talk deeper about the language in here.
Is there a solution for this problem?
First of all, in my example it is fairly easy to find out that there is a difference between the airplane and the apiAirplane. For this I simply log both objects into the console.
const airplane: Airplane = new Airplane();
airplane.name = "XCF345";
let apiAirplane: Airplane;
apiAirplane = getAirplaneFromAPI<Airplane>();
console.log(airplane);
console.log(apiAirplane);
This is the result when i execute the code:
ts-node object.ts
Airplane { isFlying: false, name: 'XCF345' }
{ isFlying: false, name: 'WER875' }
Here I see that the first one has a class in front of it object properties and the second one does not. For all power users of Google, here is an example search string: “convert javascript object into class instance”.
First google result suggests using Object.assign(this, obj).
So let’s give that a try. For example like this:
let apiAirplane: Airplane = new Airplane();
const apiObject = getAirplaneFromAPI<Airplane>();
Object.assign(apiAirplane, apiObject);
And there you go, both planes can now take off:
Airplane { isFlying: false, name: 'XCF345' }
Airplane state before takeoff: false
Airplane state after takeoff: true
Airplane { isFlying: false, name: 'WER875' }
Airplane state before takeoff: false
Airplane state after takeoff: true
I know that this has only scratched the surface. But I think the important message should be: You are coding in Javascript or Typescript? Then always have a look where your object is assigned or if you create it yourself and find out for yourself if it is exactly what it says and if it is a class instance or just a “stupid” object.