Moving ASP.NET Single Page Application to TypeScript

I just moved ASP.NET Single Page Application out-of-box example to TypeScript. My idea was to try out how TypeScript works and what it means to move existing JavaScript application to it. Here is overview about what I did and what was my experience with my first steps on TypeScript.

Prerequisites

You need the following pieces of software installed on your machine to use my code:

What is TypeScript?

TypeScript is superset of JavaScript that is targeted to building large JavaScript application. It supports type hinting, modules and variable scopes. It is not separate language – every JavaScript application is TypeScript application and vice versa.

TypeScript files are “compiled” to JavaScript when you build your application and all rules given in TypeScript files are checked. Errors are shown as usual compile errors that stop the building process. It is easy to avoid mistakes this way that otherwise maybe hard to find.

TypeScript has more familiar object-oriented syntax but it doesn’t do any magic. It is close to JavaScript and to write it one must know how JavaScript works.

ASP.NET SPA default application

ASP.NET Single Page Application JavaScript filesWhen you create new ASP.NET Single Page Application project it has some files already there that make up the sample application.

Besides Web API controllers that are used to move data between user interface and server there are some JavaScript files under Scripts folder:

  • todo.binding.js – additional Knockout bindings for sample application (clearing textbox when user clicks enter, placeholder text functionality for non-HTML5 browsers etc),
     
  • todo.datacontext.js – one-class-data-layer that coordinates all data operations,
     
  • todo.model.js – model for sample application, contains classes used by sample application (take these classes as models and not as browser-side version of domain classes),
     
  • todo.viewmodel.js – view model for sample application.

Some things I don’t like about these files is the fact that each of these files contains one large function that has classes defined inside it. I would like if code is written more object-oriented way and I don’t keep have to keep in mind the fact that these files are actually one big JavaScript calls.

Moving to TypeScript

I moved JavaScript files mentioned above to TypeScript. The only exception was bindings file as it was simple and straight-forward enough. For other files I made the following steps:

  • create new TypeScript files (I prefer one class per file style),
  • add module definition,
  • port code from current file to TypeScript class,
  • make sure all code that is not part of JavaScript class gets called,
  • add new file to JavaScripts bundle,
  • uncomment code in old file,
  • run application and test.

There are some things I made differently:

  • there is one class per TypeScript file,
  • I added special Application.ts file that contains application initialization code.

I like when class files are clean and doesn’t contain code that starts doing something when loaded. If all initialization code is in same file (Application.ts in my case) then it makes me easier to control how application is initialized and started.

Example class

As an example of TypeScript class let’s take TodoItem.ts from my application:


/// <reference path="DataContext.ts" /> declare var ko; declare var todoApp; module TodoApp {
    
export class
TodoItem {
         datacontext: TodoApp.DataContext;
         TodoItemId:
any
;
         Title:
any
;
         IsDone:
any
;
         TodoListId:
any
;

        
public ErrorMessage: any
;
        
public
save;

        
constructor
(data, datacontext: TodoApp.DataContext) {
            
this
.datacontext = datacontext;
             data = data || {};


            
// Persisted properties             this
.TodoItemId = data.TodoItemId;
           
this
.Title = ko.observable(data.Title);
           
this
.IsDone = ko.observable(data.IsDone);
           
this
.TodoListId = data.TodoListId;

            
// Non-persisted properties             this
.ErrorMessage = ko.observable();

            
var _self = this
;
            
this.save = function
() {
                
return
todoApp.datacontext.saveChangedTodoItem(_self);
             }

            
// Auto-save when these properties change             this.IsDone.subscribe(this
.save);
            
this.Title.subscribe(this.save);
         }
     } }

And this is the JavaScript that is generated from TypeScript above:


var TodoApp;
(
function
(TodoApp) {
    
var TodoItem = (function
() {
        
function
TodoItem(data, datacontext) {
            
this
.datacontext = datacontext;
             data = data || {};
            
this
.TodoItemId = data.TodoItemId;
            
this
.Title = ko.observable(data.Title);
            
this
.IsDone = ko.observable(data.IsDone);
            
this
.TodoListId = data.TodoListId;
            
this
.ErrorMessage = ko.observable();
            
var _self = this
;
            
this.save = function
() {
                
return
todoApp.datacontext.saveChangedTodoItem(_self);
             };
            
this.IsDone.subscribe(this
.save);
            
this.Title.subscribe(this
.save);
         }
        
return TodoItem;
     })();
     TodoApp.TodoItem = TodoItem;
})(TodoApp || (TodoApp = {}));

One thing is miss is some good way to write object-oriented code without using typical self-closure hack to refer to class instance safely in methods.

Configuring bundling

Although your TypeScript files are not downloaded to browser when page loads you have to load JavaScript files that were generated by TypeScript. I’m doing the loading of these files in bundling configuration of my web application. It’s done in BundleConfig.cs file under App_Start folder.


bundles.Add(new ScriptBundle("~/bundles/todo").Include(
    
"~/Scripts/app/todo.bindings.js"
,
    
"~/Scripts/app/DataContext.js"
,
    
"~/Scripts/app/TodoItem.js"
,
    
"~/Scripts/app/TodoList.js"
,
    
"~/Scripts/app/TodoListViewModel.js"
,
    
"~/Scripts/app/Application.js"
));

JavaScript files generated by TypeScript are in same folder with TypeScript files and they have same name as TypeScript files.

Conclusion

TypeScript is cool thing and I think it’s very good fit for large JavaScript applications as it helps to detect places in code where things are not used the way that was expected. As TypeScript is superset of JavaScript it is easy to convert between those two and also language rules are almost the same. It wasn’t very hard to convert SPA default application JavaScript to TypeScript and I am sure that over next years we will see JavaScript projects that make heavy use of TypeScript.

3 Comments

  • If I may ask, why not put save as a normal function as well instead of defining it inside the constructor?

  • Thanks for good question, Mladen! Normal function in TypeScript means function definition through prototype in JavaScript. In this case I cannot use "self" anymore as "this" may refer to completely other object that the one where method belongs.

    When Knockout calls some function then for this function "this" is the current object in Knockout. If this function is defined to "class" through prototype then I cannot access "class" members anymore.

    To avoid this problem I'm using closures.

  • You don't have to write _self = this stuff in TypeScript. Use arrow-functions instead.

Comments have been disabled for this content.