Search code examples
typescripttypescript2.0angular-httpclienttypescript-types

Typescript with Angular HttpClient ErrorType ... is not a function when calling class method


I'm facing some weird (to me) problem with objects and types in typescript/angular.

I have some class describing my objects received from remote API:

export class Point {
    lat:number;
    lng:number;
    name:string;

    public doSomething() {
        console.log("doSomething called");
    }
}

I'm using HttpClient to get objects from API:

constructor(private http:HttpClient) {}

getPointsAsync(callback: (points: Point[]) => {}) {
    this.http.get<Point[]>(`${environment.apiUrl}/api/points`)
    .subscribe(
        (result: Point[]) => {
            //do something with data
            callback(result);
        },
        error => { //some error handling
        }
    );
}

My problem is that when I try to call method doSomething on one of my Point from array

var firstPoint = points[0];
firstPoint.doSomething()

I get some weird error on console:

ERROR TypeError: firstPoint.doSomething is not a function

I'm not using Typescript and Angular for very long so I'm assuming that it is something wrong with my code but I couldn't find answer to my issue. Could you give me some advice?


Solution

  • Object.assign() will solve your current problem, but not if you have nested objects. Then you will have to do Object.assign() for each nested object as well, which can get tedious if you have to do this in multiple places in your codebase.

    I suggest an alternative: class-transformer With this you can mark your nested fields with annotations that tell the compiler how to create the nested objects as well. With this you only need to use the plainToClass() method to map your top level object and all the underlying fields will also have the correct types/objects.

    Example

    Let's say we have two classes:

    class Parent {
        name: string;
        child: Child;
    
        public getText(): string {
            return 'parent text';
        }
    }
    
    class Child{
        name: string;
    
        public getText(): string {
            return 'child text';
        }
    }
    

    The first case we already know doesn't work:

    let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
    let parent: Parent = parentJson; // note: compiler accepts this because parentJson is any.  
            // If we tried to assign the json structure directly to 'parent' it would fail because the compiler knows that the method getText() is missing!
    
    console.log(parent.getText()); // throws the error that parent.getText() is not a function as expected
    

    Second case using Object.assign():

    let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
    let parent: Parent = Object.assign(parentJson); 
    
    console.log(parent.getText()); // this works
    console.log(parent.child.getText()); // throws error that parent.child.getText() is not a function!
    

    to make it work, we would have to do the following:

    let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
    let parent: Parent = Object.assign(parentJson);
    parent.child = Object.assign(parentJson.child);
    
    console.log(parent.getText()); // this works
    console.log(parent.child.getText()); // this works
    

    Third case with class-transformer:

    First modify the parent class so that the child mapping is defined:

    class Parent {
        name: string;
        @Type(() => Child)
        child: Child;
    
        public getText(): string {
            return 'parent text';
        }
    }
    

    then you can map to the parent object:

    let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
    let parent: Parent = plainToClass(Parent, parentJson);
    
    console.log(parent.getText()); // this works
    console.log(parent.child.getText()); // this works