Búsqueda de sitios web

typescript2.0 - Typescript con Angular HttpClient ErrorType ... no es una función al llamar al método de la clase


Me estoy enfrentando a un problema extraño (para mí) con objetos y tipos en TypeScript/Angular.

Tengo una clase que describe mis objetos recibidos de una API remota:

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

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

Estoy usando HttpClient para obtener objetos de la 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
        }
    );
}

Mi problema es que cuando intento llamar al método doSomething en uno de mis Point del array

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

Recibo un error extraño en la consola:

ERROR TypeError: firstPoint.doSomething is not a function

No he estado usando Typescript y Angular por mucho tiempo, así que supongo que hay algo mal con mi código, pero no pude encontrar una respuesta a mi problema. ¿Podrías darme algún consejo?

Respuesta #1

El problema es que en realidad no obtienes instancias de Point del servicio web. Obtienes un objeto JSON que tiene los campos de la clase, pero no los métodos. Puedes instanciar la clase y usar Object.assign para asignar los valores del literal de objeto a cada instancia de Point

getPointsAsync(callback: (points: Partial<Point>[]) => {}) {
    this.http.get<Point[]>(`${environment.apiUrl}/api/points`)
        .subscribe(
            (result: Partial<Point>[]) => {
                let points = result.map(p=> Object.assign(new Point(), p));
                callback(points);
            },
            error => { //some error handling
            }
        );
}

Respuesta #2

Object.assign() resolverá tu problema actual, pero no si tienes objetos anidados. Entonces tendrás que hacer Object.assign() para cada objeto anidado también, lo cual puede volverse tedioso si tienes que hacer esto en múltiples lugares de tu base de código.

Sugiero una alternativa: class-transformer. Con esto puedes marcar tus campos anidados con anotaciones que le dicen al compilador cómo crear también los objetos anidados. Con esto solo necesitas usar el método plainToClass() para mapear tu objeto de nivel superior y todos los campos subyacentes también tendrán los tipos/objetos correctos.

Ejemplo

Digamos que tenemos dos clases:

class Parent {
    name: string;
    child: Child;

    public getText(): string {
        return 'parent text';
    }
}

class Child{
    name: string;

    public getText(): string {
        return 'child text';
    }
}

El primer caso que ya sabemos que no funciona:

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

Segundo caso usando 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!

para que funcione, tendríamos que hacer lo siguiente:

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

Tercer caso con class-transformer:

Primero modifica la clase padre para que se defina el mapeo hijo:

class Parent {
    name: string;
    @Type(() => Child)
    child: Child;

    public getText(): string {
        return 'parent text';
    }
}

entonces puedes mapear al objeto padre:

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

Artículos relacionados