Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4, Property 'XX' is missing in type '{xx, xx}'

I am new to this kind of type scripting. Here is my model class:

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;
     }
}

And, below is my initializing, referred to Angular tutorial:

 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") }
];

The model is expected to be used like this in template:

{{ project.getDaysRemaining() }}

But, I am getting the error:

Property 'getDaysRemaining' is missing in type '{ projectId: number; description: string; startDate: Date; endDate: Date; }'.

Why do I have to initialize the function in TypeScript? Or, am I missing something?

like image 238
M. Ko Avatar asked May 12 '17 05:05

M. Ko


1 Answers

Your problem has to do with how a class is instantiated.

Instead of:

 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") }
];

you should do:

 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")
];

Explanation

Project is a class. getDaysRemaining() is a member of that class. To create an instance of a class you can use new Project(...).

{ projectId: 1, ... } is an object that is not an instance of class Project. Therefore all the members are defined between the brackets as properties.

You can check if { projectId: 1, ... } is an instance of Project by simply { project1: 1, ... } instanceof Project - resulting false.

ES6? TypeScript has much of the syntax in common with the latest advances in JavaScript language. You can define classes in ES6 similar to how you do in TypeScript. Of course, excepting the types and modifiers as JavaScript is not a statically-typed language, but a dynamically-typed one.

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;
     }
}

In ES6 you can do new Project(), yielding undefined for all arguments, but in TypeScript cannot because there are missing constructor arguments, and this fact is checked at compile time, and if there is a good IDE which checks TypeScript syntax, then also at design time.

One option to allow this in TypeScript is to define optional arguments like so (see the question mark near the argument):

 constructor(
      public projectId?: number,
      public description?: string,
      public startDate?: Date,
      public endDate?: Date
 ) { }

Common problem - obtain some data from a source, like a web service

You might encounter the case when you get your Project data from a web service. In that case the data will be deserialized into plain objects { projectId: 1, ... }. Obtaining Project instances from that data cannot be done implicitly and additional work needs to be done.

The simplest is passing each property from one side to the other in a factory method:

class Project implements IProject {
    ...
    static create(data: IProject) {
        return new Project(data.projectId, data.description, data.startDate, data.endDate);
    }
    ...
}

and you can use an interface (instead of any) for your data to provide additional help while typing, and also implementing it (class Project implements IProject):

interface IProject {
    projectId: number;
    description: string;
    startDate: Date;
    endDate: Date;
}

Another option is to use optional arguments, but this might not apply for all cases:

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);
     }
     ...
}

thus the interface would have optional arguments (check the question mark) if is to be implemented like above:

interface IProject {
    projectId?: number;
    description?: string;
    startDate?: Date;
    endDate?: Date;
}

Compilation and interfaces while the TypeScript is transpiled into JavaScript all the interfaces will be removed from the final code. There is no "interface" concept in JavaScript. This is a construct of TypeScript which purpose is to provide additional type information.

try to be SOLID - S = Single Responsibility Principle

In the above code Project has two responsibilities (not one). 1st is to hold some Project data. 2nd is to compute the remaining days - getDaysRemaining. This might be too much for a single class.

One clean solution could be to split this in two.

Have an interface to represent the data of the Project:

interface IProject {
    projectId: number;
    description: string;
    startDate: Date;
    endDate: Date;
}

And move the getDaysRemaining() to another class like:

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;
    }
}

This has the advantage that the the data - IProject - is separate and additional functionality can be placed in other classes which do their own job - like - ProjectSchedulingService.

Another advantage is that IProject can help working with plain objects instead of instantiating additional classes. You can get data from a web service, casting it to an interface and work in a typed fashion.

Another advantage is that the business can be split in multiple classes - like ProjectSchedulingService - thus being able to be unit tested individually instead of having a heavy monolithic class with data and all the related business in it.

Another advantage is that having multiple classes helps defining dependencies between classes more easily, thus keeping business semantics, and be able to mock all dependencies of a class and unit test the business of the class at hand.

Take Angular for exmple, you can have multiple services (classes) which can be injected as needed. Having one monolithic service would mean passing something which just a portion of it is required. Thus breaking also the interface segregation.

This might sound silly at first, as the code is so small, but as the time goes this will keep in growing and next developers might still keep developing in the same monolithic way instead of refactoring. After some time, many estimates will increase or be broken because of the difficulty of maintaining the code. Also cyclomatic complexity becomes worst and also the code coverage for unit tests.

like image 158
andreim Avatar answered Nov 02 '22 22:11

andreim