loading...
Cover image for Sorry C# and Java developers, this is not how TypeScript works
This is Learning

Sorry C# and Java developers, this is not how TypeScript works

layzee profile image Lars Gyrup Brink Nielsen Updated on ・3 min read

Cover photo by Lina Trochez on Unsplash.

So you took a look at TypeScript. Classes and a C-like syntax. Seems easy enough.

Your manager asks you to rush the edit todo item feature in your brand new TypeScript application.

Boss meme
Boss meme by Make a Meme

On the server-side you have this C# class.

// TodoItem.cs
public class TodoItem
{
    public string Id { get; set; }
    public bool IsDone { get; set; }
    public string Title { get; set; }

    public async Task Save()
    {
        // Write to database
    }
}
Enter fullscreen mode Exit fullscreen mode

C#: Todo item.

On the client-side, you create a similar class.

// todo-item.ts
class TodoItem {
  id: string;
  isDone: boolean;
  title: string;

  save(): Promise<void> {
    return fetch("/todo/" + this.id, {
      body: JSON.stringify(this),
      method: "POST",
    })
      .then(() => undefined);
  }
}
Enter fullscreen mode Exit fullscreen mode

TypeScript: Todo item.

Not too bad.

We have a view for editing a todo item. The view class reads the todo item from the server using fetch which returns an HttpResponse.

// edit-todo-item-view.ts
class EditTodoItemView {
  todoItem: TodoItem;

  onInitialize(id: string): Promise<void> {
    return this.readTodoItem(id)
      .then(todoItem => this.todoItem = todoItem)
      .then(() => undefined);
  }

  readTodoItem(id: string): Promise<TodoItem> {
    return fetch("/todo/" + id)
      .then((response: HttpResponse) => response.json());
  }

  saveTodoItem(): Promise<void> {
    return this.todoItem.save();
  }
}
Enter fullscreen mode Exit fullscreen mode

TypeScript: Edit todo item view.

HttpResponses can be parsed as JSON by using the HttpResponse#json method.

We add the TodoItem type to the returned promise of the readTodoItem method.

The application transpiles to JavaScript without errors, so we deploy it on a web server.

We tell our manager that the edit todo item feature is done and move on to the next task.

Borat meme
Borat meme by Meme Generator

Everything is fine… Until we start getting bug reports from users telling that they edited a todo item and saved it. But when they navigated back to the todo list, the todo item was not updated.

Bug meme
Bug meme by Nepho

But… It compiled! Did TypeScript let us down?

JavaScript is a loosely typed programming language and TypeScript does not change that; even if it seems that way.

TypeScript was not lying to us. We were lying to TypeScript. It is easy to miss, but we told TypeScript to give the JSON object the TodoItem type.

The problem is that the JSON object was never constructed from the TodoItem class with the new keyword. It was actually an anonymous object without access to the TodoItem prototype.

To fix the bug, we have to make a few changes.

// todo-item.ts
class TodoItem {
  id: string;
  isDone: boolean;
  title: string;

  constructor(properties) {
    this.id = properties.id;
    this.isDone = properties.isDone;
    this.title = properties.title;
  }

  save(): Promise<void> {
    return fetch("/todo/" + this.id, {
      body: JSON.stringify(this),
      method: "POST",
    })
      .then(() => undefined);
  }
}
Enter fullscreen mode Exit fullscreen mode

TypeScript: Todo item with constructor.

We add a constructor that we can pass the JSON object to and get back an instance of TodoItem.

// edit-todo-item-view.ts
class EditTodoItemView {
  todoItem: TodoItem;

  onInitialize(id: string): Promise<void> {
    return this.readTodoItem(id)
      .then(todoItem => this.todoItem = todoItem)
      .then(() => undefined);
  }

  readTodoItem(id: string): Promise<TodoItem> {
    return fetch("/todo/" + id)
      .then((response: HttpResponse) => response.json())
      .then(json => new TodoItem(json));
  }

  saveTodoItem(): Promise<void> {
    return this.todoItem.save();
  }
}
Enter fullscreen mode Exit fullscreen mode

TypeScript: Edit todo item view using the new keyword.

After reading the JSON from the server, we pass it to the TodoItem constructor and get an actual instance of the class back.

We transpile the code, deploy it to the web server and this time we remember to test it… In production of course 🤪

Obama meme
Obama meme by Meme Generator.

Dedicated to all the hard-working back-end developers who are forced to learn client-side web development.

Discussion

pic
Editor guide
Collapse
tunaxor profile image
Angel D. Munoz

Nice post, even if I've been using typescript almost since it came out I always get bitten by these kinds of issues

What I tried to do to prevent these issues is to favor interfaces vs classes
whenever I know I'm dealing with an object or data manipulation I try to use interfaces but for functionality (views or viewmodels) I do try to favor classes.

Collapse
cubiclebuddha profile image
Cubicle Buddha

Ditto. This whole issue could be prevented by favoring a data-oriented approach. In other words, don’t mix behavior (functions) and state (primitive properties).

So simply represent the data coming back as interface of json data and move faster. :)

Collapse
layzee profile image
Lars Gyrup Brink Nielsen Author

I agree. I would prefer to not create model classes that have methods for synchronizing with a server like the TypeScript TodoItem class in this example.

However, code like this is still seen in the wild, and I wanted a simple example. In fact, it's very common practice albeit not a good one to use ORMs on the server-side.

Your comments brings up some very valid points for discussion of code quality and software architecture.

Collapse
ltvan profile image
Van Ly
  1. Separate web service proxy with data transfer object from the view model. Use swagger to generate web service proxy. Don't call API directly without a proxy class, then you will never forget that.
  2. Never ship a code without testing it. :-)
Collapse
emiliosp profile image
emilioSp

Nice example! Probably, I would have used async await constructor to avoid callbacks.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen Author

Client-side application frameworks usually have an initialization hook which I why I used this example.

Starting side effects on a constructor is also considered poor design.

Collapse
emiliosp profile image
emilioSp

I’m sorry. I was not clear.

What I meant to say it’s to use the async await syntax instead of cascading ‘then’.

developer.mozilla.org/en-US/docs/W...

The class constructor has nothing to do with this.

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen Author

Using async-await is largely a preference. If we us it, we have to wrap most calls in try-catch.

Collapse
jckodel profile image
J.C.Ködel

1) For most transport classes (even with C# or Java) you should use plain classes (i.e.: no methods on them). Typescript Interfaces are great for this.

2) If you really want typed transports, this is an awesome libray that reads some dynamic value and casts it to typed value: github.com/typestack/class-transfo... It is very usefull for those computed properties =)

Collapse
layzee profile image
Lars Gyrup Brink Nielsen Author

Thanks for your comments and suggestions, everyone 😊 I'm a frontend developer, so of course I would never suffer from anonymous objects typed as class instances 😇