-1

In an Angular application, I'm getting a JSON from the server which contains a date, the date is like "2022-08-05" in developers tools -> network -> response.

Using an HTTP request, I'm putting the JSON inside the following interface:

export interface Movie {
  id: number
  title: string
  releaseDate: Date
  director: string
  description: string
}

When performing movie.releaseDate.getFullYear(), the following error shows in console: movie_r2.releaseDate.getFullYear is not a function

Then, I tried using a class with a property decorator but that doesn't work either.
Afterwards, I tried with getter and setter, but I get the same error.


// function asDate(target: Object, propertyKey: string) {
//   let value: string;
//   console.log("asDate() called");
//
//   Object.defineProperty(target, propertyKey, {
//     get: () => new Date(value),
//     set: (newValue) => value = newValue
//   });
// }

export class Movie {
  id: number
  title: string
  // @asDate
  private _releaseDate: Date
  director: string
  description: string


  constructor(id: number, title: string, releaseDate: Date, director: string, description: string) {
    console.log("constructor called");
    this.id = id;
    this.title = title;
    this._releaseDate = new Date(releaseDate);
    this.director = director;
    this.description = description;
  }

  get releaseDate(): Date {
    console.log("called getter");
    return new Date(this._releaseDate);
  }

  set releaseDate(releaseDate: Date) {
    console.log("called setter");
    this._releaseDate = new Date(releaseDate);
  }
}

Why are the console.log statements in the descriptor, constructor, getter, setter never called?

How to write some code at the level of the Movie class/interface/type such that, for any HTTP request which contains the type Movie, the field releaseDate will contain an object of type Date (and not a string)? I have more than one HTTP request with a Movie interface/class, so I'm interested in a solution that is independent of the HTTP request. Meaning that I write it once, and it works at any request.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
sandrino
  • 65
  • 1
  • 7
  • https://stackoverflow.com/a/70978645/876038 map your date to a Date object – Alexander Aug 05 '22 at 09:46
  • Your interface is a lie - that's **not** what you get from the API. TypeScript doesn't _exist_ at runtime, it can't do any kind of casting or conversion. – jonrsharpe Aug 05 '22 at 09:48
  • @Alexander, that solution implies writing it for every http request. It should be written just once and work for any http request. – sandrino Aug 05 '22 at 09:52
  • @jonrsharpe, I see, but still, why the property decorator or the getter/setter doesn't work? – sandrino Aug 05 '22 at 09:54
  • because if you don't create an object of the class by calling new, you won't have the object of that class, simple assigning the type, won't make anything really – Maciej Wojcik Aug 05 '22 at 10:08
  • What do you mean it doesn't work? Do you actually new up Movie anywhere? Also note you convert the string to a date then the date to a date again, which seems inefficient. – jonrsharpe Aug 05 '22 at 10:10
  • @MaciejWojcik, then how can I interfere with the value assignation in the get method of the HttpClient, such that, whenever such request is performed, releaseDate is converted from string to Date? – sandrino Aug 05 '22 at 10:20
  • I put my proposition in the Answer - you have to use the rxjs map operator. – Maciej Wojcik Aug 05 '22 at 10:22
  • @jonrsharpe, I don't use new Movie, but I use the `get releaseDate()` property and it never gets called. – sandrino Aug 05 '22 at 10:23
  • If you never have `new Movie`, then you **don't** use `get releaseDate`. – jonrsharpe Aug 05 '22 at 10:23
  • @jonrsharpe, but the get in the http request is typed with the Movie class. Doesn't that mean that the object is of type Movie and that the property should work? – sandrino Aug 05 '22 at 10:24
  • 2
    No. Again, TypeScript **does not exist** at runtime. That's just a hint to the compiler (a lie, because you _don't_ get an instance of the Movie class - you get a plain old JS object). See e.g. https://github.com/angular/angular/issues/25401 - this has been deliberately called out in the docs. If you want the string to be converted to a Date, you have to write _and use_ code to do that. – jonrsharpe Aug 05 '22 at 10:26

1 Answers1

1

Forcing type any (that is what comes from http request) to your Type (interface or class) doesn't mean it will automatically convert any fields or even check if the assumption is correct.

In order to actually convert string to Date, you need to invoke the transformation code. For instance you can do the following when working with interface:

this.http.get('api.url').pipe(
  map(response => ({
    ...response, 
    releaseDate: new Date(response.releaseDate)
  }),
);

or the following, when using the class:

this.http.get('api.url').pipe(
  map(response => new Movie(response.id, response.releaseDate, ...)),
);

to be 100% on the same page: How does this work:

// this is a type that comes from API:
export interface MovieDTO {
  id: number
  title: string
  releaseDate: string
  ...
}

// this is a type you would like to have after the transformation:
export interface Movie {
  id: number
  title: string
  releaseDate: Date
  ...
}

// in order to map MovieDTO to Movie you need to actually invoke the mapping. 
// to do so, you can use for instance the spread operator, as follows:

// some.service.ts
getMovie(): Observable<Movie> {
  return this.http.get<MovieDTO>('api.url').pipe(
    map(response => ({
      ...response, 
      releaseDate: new Date(response.releaseDate)
  }),
);

}

Or if you want to go with the class approach, here is how you can make it working:

// this is a type that comes from API:
export interface MovieDTO {
  id: number
  title: string
  releaseDate: string
  ...
}

// this is your class:
export class Movie {
  public id: string;
  public title:string;
  public releaseDate: Date;

  constructor(data: MovieDTO){
    this.id = data.id;
    this.title = data.title;
    this.releaseDate = new Date(data.releaseDate);
  }
}

// in order to map MovieDTO to Movie you need to actually create the object by calling new(...). 

// some.service.ts
getMovie(): Observable<Movie> {
  return this.http.get<MovieDTO>('api.url').pipe(
    map(response => new Movie(response),
  );
}

Maciej Wojcik
  • 2,115
  • 2
  • 28
  • 47