2

I make following request in Angular and save the response in a variable:

conversations: Conversation[];

// ChatService
getConversations() {
    return this.http.get<Conversation[]>('/chat/conversations');
}

this.chatService.getConversations().subscribe(
    (response: Conversation[]) => this.conversations = response
);

This is the JSON Data from the Server:

[  
   {  
      "chatRoomId":"096b8be1-2411-4cb1-94e0-ed96c51c23d8",
      "name":"Bar",
      "profilePicture":"...",
      "conversation":[  
         {  
            "name":"Bar",
            "message":"Hello!",
            "createdAt":"2018-09-30T06:50:49.000+0000"
         },
         {  
            "name":"Foo",
            "message":"Hi",
            "createdAt":"2018-09-30T11:49:05.000+0000"
         }
      ]
   }
]

TypeScript Models:

export interface Conversation {
  chatRoomId: string;
  name: string;
  profilePicture: string;
  conversation: ChatMessage[]
}

export interface ChatMessage {
  name: string;
  message: string;
  createdAt: string;
}

The problem:

If i output the variable in the console, the conversation (conversation: ChatMessage[]) is an empty array element.

And if i make the request with this.http.get<any>('...'), the conversation get's stored as expected.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Prolativ
  • 727
  • 1
  • 7
  • 12

1 Answers1

1

There's a few ways you can do what you're trying to do but from my experience, using this library has been the easiest: https://github.com/typestack/class-transformer

Here's how it would work in your situation. First I would change your interfaces to be Typescript Classes.

import { Type } from 'class-transformer';

export class Conversation {
    chatRoomId: string;
    name: string;
    profilePicture: string;

    @Type(() => ChatMessage)
    conversation: ChatMessage[]

    constructor(args: Conversation) {
      Object.assign(this, args);
    }
}

export class ChatMessage {
    name: string;
    message: string;
    createdAt: string;

    constructor(args: ChatMessage) {
      Object.assign(this, args);
    }
}

A few things changed from when these were interfaces::

  1. The use of the @Type decorator comes from the class-transformer module. This allows you to transform nested objects. Here is the documentation: https://github.com/typestack/class-transformer#working-with-nested-objects
  2. We've added a constructor which allows you to create instances of these Classes and pass through attributes to them of their own respective types. Take a look at this post Converting httpClient answer to model objects [Angular 6] as it shines more light onto whats happening here.

Then within your service this is how your code changes:

import { plainToClass } from 'class-transformer';    

conversations: Conversation[];

// ChatService
getConversations() {
    return this.http.get<Conversation[]>('/chat/conversations');
}

this.chatService.getConversations().subscribe(
    (response: Conversation[]) => {
        this.conversations = plainToClass(Conversation, response) as Conversation[]
    }
);

plainToClass will take the raw JSON response and transform it into instances of your Conversation Class. If you console.log out this.conversations you will see that it returns an Array of Conversations that each have an array attribute called conversations of type ChatMessage.

jetset
  • 882
  • 1
  • 9
  • 15
  • this post is utterly misleading. nothing of the stated meets the issue. interfaces do not do anything at runtime (they dont even exist at runtime to be exact) - and changing the type to any will not change the runtime experience - ever. You are speaking of TypeScript checking the JSON response, but TypeScript doesn't do anything at runtime, it is a transpiler. – Patrick Kelleter Sep 30 '18 at 16:36
  • @PatrickKelleter How does this look now? You are right, typescript doesn't do any runtime type checking that's my bad. Using Javascript classes here is what I would do, since it allows you to organize the service response json and clearly define nested classes. – jetset Sep 30 '18 at 17:34
  • I can not say that using classes is wrong by any mean - but i can say that i actually prefer interfaces over classes whenever possible. there are plenty of reasons for that - too many for this comment - but the most important one is that i dislike the idea of oop (just as many others do nowadays) for typesafety it is easier and less verbose to use interfaces over classes, so if you do not oop you may just use interfaces in 90% of the time instead. there are definitely use cases for classes, but i would say they are about 10% compared to interfaces – Patrick Kelleter Oct 01 '18 at 17:28
  • 1
    btw it is worth mentioning that i used classes a lot in the beginning (typescript 1.1 is when i started using it) since i came from java and was used to that design pattern. but over the time i replaced more and more of my code by interfaces and type definitions. the code turns out to be much leaner if you stick to that paradigm. actually there are plenty of blog posts comparing class/interface/type, you may just give it a shot at google. also the typescript docs are very insightful, personally I learned a lot from that source and, as mentioned, blog posts. – Patrick Kelleter Oct 01 '18 at 17:32