typescript - Angular 4, Falta la propiedad 'XX' en el tipo '{xx, xx}'
Soy nuevo en este tipo de scripting. Aquí está mi clase de modelo:
export class Project {
constructor(
public projectId: number,
public description: string,
public startDate: Date,
public endDate: Date
) {}
getDaysRemaining() {
var result = this.endDate.valueOf() - Date.now().valueOf();
result = Math.ceil(result / (1000 * 3600 * 24));
return result < 0 ? 0 : result;
}
}
Y, a continuación está mi inicialización, referida al tutorial de Angular:
let PROJECTS: Project[] = [
{ projectId: 1, description: "Sample", startDate: new Date("2016-12-12"), endDate: new Date("2017-1-13") },
{ projectId: 2, description: "Sample 2", startDate: new Date("2016-12-12"), endDate: new Date("2017-1-13") }
];
Se espera que el modelo se use así en la plantilla:
{{ project.getDaysRemaining() }}
Pero, estoy recibiendo el error:
La propiedad 'getDaysRemaining' falta en el tipo '{ projectId: number; description: string; startDate: Date; endDate: Date; }'.
¿Por qué tengo que inicializar la función en TypeScript? O, ¿me estoy perdiendo de algo?
Respuesta #1
Tu problema tiene que ver con cómo se instancia una clase.
En lugar de:
let PROJECTS: Project[] = [
{ projectId: 1, description: "Sample", startDate: new Date("2016-12-12"), endDate: new Date("2017-1-13") },
{ projectId: 2, description: "Sample 2", startDate: new Date("2016-12-12"), endDate: new Date("2017-1-13") }
];
deberías hacer:
let PROJECTS: Project[] = [
new Project(1, "Sample", new Date("2016-12-12"), new Date("2017-1-13") ),
new Project(2, "Sample 2", new Date("2016-12-12"), new Date("2017-1-13")
];
Explicación
Project es una clase. getDaysRemaining() es un miembro de esa clase. Para crear una instancia de una clase puedes usar new Project(...).
{ projectId: 1, ... } es un objeto que no es una instancia de la clase Project. Por lo tanto, todos los miembros se definen entre los corchetes como propiedades.
Puedes comprobar si { projectId: 1, ... } es una instancia de Project simplemente con { project1: 1, ... } instanceof Project - lo que resulta en false.
ES6? TypeScript tiene gran parte de la sintaxis en común con los últimos avances en el lenguaje JavaScript. Puedes definir clases en ES6 de manera similar a cómo lo haces en TypeScript. Por supuesto, exceptuando los tipos y modificadores, ya que JavaScript no es un lenguaje de tipos estáticos, sino uno de tipos dinámicos.
class Project {
constructor(projectId, description, startDate, endDate) {
this.projectId = projectId;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
}
getDaysRemaining() {
var result = this.endDate.valueOf() - Date.now().valueOf();
result = Math.ceil(result / (1000 * 3600 * 24));
return result < 0 ? 0 : result;
}
}
En ES6 puedes hacer new Project(), obteniendo undefined para todos los argumentos, pero en TypeScript no se puede porque faltan argumentos en el constructor, y este hecho se verifica en tiempo de compilación, y si hay un buen IDE que verifica la sintaxis de TypeScript, entonces también en tiempo de diseño.
Una opción para permitir esto en TypeScript es definir argumentos opcionales de la siguiente manera (vea el signo de interrogación cerca del argumento):
constructor(
public projectId?: number,
public description?: string,
public startDate?: Date,
public endDate?: Date
) { }
Problema común - obtener algunos datos de una fuente, como un servicio web
Podrías encontrarte con el caso en que obtienes tus datos de Project de un servicio web. En ese caso, los datos serán deserializados en objetos simples { projectId: 1, ... }. Obtener instancias de Project a partir de esos datos no se puede hacer de manera implícita y se necesita hacer trabajo adicional.
Lo más sencillo es pasar cada propiedad de un lado a otro en un método de fábrica:
class Project implements IProject {
...
static create(data: IProject) {
return new Project(data.projectId, data.description, data.startDate, data.endDate);
}
...
}
y puedes usar una interfaz (en lugar de any) para tus datos para proporcionar ayuda adicional mientras escribes, y también implementándola (class Project implements IProject):
interface IProject {
projectId: number;
description: string;
startDate: Date;
endDate: Date;
}
Otra opción es usar argumentos opcionales, pero esto podría no aplicarse en todos los casos:
class Project implements IProject {
...
constructor(
public projectId?: number,
public description?: string,
public startDate?: Date,
public endDate?: Date
) { }
static create(data: IProject) {
return Object.assign(new Project(), data);
}
...
}
por lo tanto, la interfaz tendría argumentos opcionales (fíjate en el signo de interrogación) si se va a implementar como arriba:
interface IProject {
projectId?: number;
description?: string;
startDate?: Date;
endDate?: Date;
}
Compilación e interfaces mientras TypeScript se transpila a JavaScript, todas las interfaces serán eliminadas del código final. No existe el concepto de "interface" en JavaScript. Esto es un constructo de TypeScript cuyo propósito es proporcionar información adicional de tipo.
intenta ser SÓLIDO - S=Principio de Responsabilidad Única
En el código anterior, Project tiene dos responsabilidades (no una). La primera es mantener algunos datos de Project. La segunda es calcular los días restantes - getDaysRemaining. Esto podría ser demasiado para una sola clase.
Una solución limpia podría ser dividir esto en dos.
Tener una interfaz para representar los datos del Project:
interface IProject {
projectId: number;
description: string;
startDate: Date;
endDate: Date;
}
Y mueve el getDaysRemaining() a otra clase como:
class ProjectSchedulingService {
getDaysRemaining(project: IProject) {
var result = project.endDate.valueOf() - Date.now().valueOf();
result = Math.ceil(result / (1000 * 3600 * 24));
return result < 0 ? 0 : result;
}
}
Esto tiene la ventaja de que los datos - IProject - están separados y la funcionalidad adicional puede colocarse en otras clases que hacen su propio trabajo - como - ProjectSchedulingService.
Otra ventaja es que IProject puede ayudar a trabajar con objetos simples en lugar de instanciar clases adicionales. Puedes obtener datos de un servicio web, convertirlos a una interfaz y trabajar de manera tipada.
Otra ventaja es que el negocio puede dividirse en múltiples clases, como ProjectSchedulingService, pudiendo así ser probado unitariamente de manera individual en lugar de tener una clase monolítica pesada con datos y todo el negocio relacionado en ella.
Otra ventaja es que tener múltiples clases ayuda a definir las dependencias entre clases más fácilmente, manteniendo así la semántica del negocio, y poder simular todas las dependencias de una clase y probar unitariamente el negocio de la clase en cuestión.
Toma Angular como ejemplo, puedes tener múltiples servicios (clases) que se pueden inyectar según sea necesario. Tener un único servicio monolítico significaría pasar algo de lo cual solo se requiere una parte. Por lo tanto, también se rompe la segregación de interfaces.
Esto podría sonar tonto al principio, ya que el código es tan pequeño, pero con el tiempo seguirá creciendo y los próximos desarrolladores podrían seguir desarrollando de la misma manera monolítica en lugar de refactorizar. Después de algún tiempo, muchas estimaciones aumentarán o se romperán debido a la dificultad de mantener el código. Además, la complejidad ciclomática empeora y también la cobertura de código para las pruebas unitarias.
