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
