Skip to main content

Converting a Large AngularJS Application to TypeScript Part 2

In part 1 I was able to take an Angular controller written in JavaScript and convert it to a TypeScript file while doing very little to change the code. In this post I am going to explore transitioning that same controller to actually use the features provided in TypeScript. This is how I left off my controller:
declare var angular: any;

(function () {
    'use strict';

 var controller: any = function($scope){
  ...
 }

 angular
        .module('app')
        .controller('controller', controller);

 controller.$inject = ["$scope"];
})();

While performing the translation from JavaScript to TypeScript, I would make sure at every step that the functionality I expected still worked, so if anything I did broke the system I would change it back and try again with another approach. Also if something seemed like it worked too easily, I would break it on purpose to make sure I wasn't getting a false result through browser caching a previously working file, just like creating a failing test first in TDD.

The first step I took was to convert the controller into a class in TypeScript. This worked out really easily and helped clean up some issues from earlier. I moved everything from the controller into the constructor of the class, so I was still not gaining much value, but I also didn't have to change much to keep things working. I was also able to pull the $inject block inside the class, which I think makes more sense than in plain JavaScript where the $inject happens outside of the function that is being injected.

declare var angular: any;

class controller{
    static $inject = ["$scope"];

    constructor($scope){
        ...
    }
}

(function () {
    'use strict';

    angular
        .module('app')
        .controller('controller', controller);
})();

The next thing I looked for was how could I pull out pieces of this controller that are used by other controllers in the system and define either a base class or an interface, or possibly both. I decided to start out with a simple id property and a function that would watch the state of the form for this controller. I created the basic interface:
interface IControl{
    id: string;
    service: any;

    formChanged: (isDirty: Boolean) => void;
}

The formChanged functionality will be the same across multiple controllers. It will call to the controller's service, but the service will be different for each controller. so I wrote a base class that implements my IControl interface:

class BaseControlController implements IControl {
    id: string;
    service: any;

    constructor(service: any) {
        this.service = service;
    }

    formChanged(isDirty: Boolean): void {
        this.service.setDirtyFlag(isDirty);
    }
}

Because each controller that uses this base class will need a service related to it, I create a constructor that will force the calling class to setup the service. I don't have a class or interface for those service yet, so I define it as "any" for now.

Next I have my actual controller extend the base class:

class controller extends BaseControlController{
    constructor($scope, service) {
      super(service);
    }
}
To this point everything is still working just as it was before, and I have actually added a bit more code than the straight JavaScript implementation, but I do have a base class defined that will allow me to reuse code in a way that I was not previously doing. Except that I am not actually calling that function yet.

I ran into some issues trying to call the formChanged function. I want it to happen inside of an angular watch, which gets defined in the constructor like this:

$scope.$watch('form.$dirty', function(newValue){
 formChanged(newValue);
});

TypeScript didn't like this because formChanged was not defined in the class, it was being defined in the base class. So I tried calling it using 'this.formChanged', same problem. So just as I had to call super in the constructor, I figured I would call 'super.formChanged' and that would work. Unfortunately, now TypeScript complained that super could not be called outside of constructors or members. I was confused by this error as the watch definition was inside the constructor, so it should have been able to access it.

What I was failing to see was that even though the watch was being defined inside of the constructor, the actual call to the base class was happening inside of the anonymous function called when the watcher notices activity. What I had to do was transform that anonymous JavaScript function into an anonymous TypeScript function:

$scope.$watch('form.$dirty', (newValue: Boolean) => {
 super.formChanged(newValue);
});

Now it was aware of super, and testing it out confirmed that it was being called at the correct time.

I have actual working TypeScript inside of my Controller and some basic inheritance in place that will allow for reuse across the project. I also have a large portion of my controller untouched and still as pure JavaScript. I would say that I am starting to see the value of converting to TypeScript. Having strongly typed object helps ensure that those objects are doing what they are supposed to do. Inheritance works in a manner I am more accustomed to coming from C# rather than trying to learn JavaScript Inheritance. Which brings up an interesting question, "would it be more productive to learn how to use JavaScript inheritance and make the JavaScript in my project more reusable than converting the project to TypeScript so that I can get that inheritance?"

I don't have an answer for that yet, I will continue to further explore converting my project over to TypeScript and see if I run into any more hurdles or find more reasons to convert to TypeScript.

Comments

Post a Comment

Popular posts from this blog

Converting a Large AngularJS Application to TypeScript Part 1

I work on a project that uses AngularJS heavily. Recently we wondered if using a preprocesser like CoffeeScript or TypeScript for our JavaScript would be beneficial. If our team is going to switch languages, we would need to be able to convert existing code over without much pain and we would have to find enough value in switching that it would be worth the conversion. I had read an article that stated that because TypeScript is a SuperSet of JavaScript, you could convert a plain JavaScript file to TypeScript by changing the extension to .ts and not much else would need to change. I wanted to test out this claim, so I took a file that I was familiar with, an Angular Controller, and tried to convert it to TypeScript to see how much effort it would take and then try to figure out where we would benefit from using TypeScript. This is what the controller JavaScript file looked like to start out with: ( function () { 'use strict' ; angular .module( 'app'

Interns: Taking off the training wheels

My intern team has been working for several weeks now on our new website. We have already completed one deployment to production and are finalizing our second one. We started with a plan to release often adding small bits of functionality as we go and so far that plan has been working really well. We already feel like we have accomplished a lot because we have completed many of our project's requirements and should easily be able to complete the rest giving us time to do even more than just the original requirements. One of the things I have had some difficulty balancing has been how much to lead the interns and how much to let them figure out on their own. In deciding what our team process should be and how we should allocate our time, I think it was important for me to do more leading. I saw some deficiencies in how we were currently working and brought up some ideas for how we could address them. We had moved into spending all our time just working through stories and did not

My idea for Hearthstone to add more deck slots

Recently someone asked the Blizzard developers for more slots for decks in the game Hearthstone. The response was that they are talking about it and looking into it, but no decision has been made yet. One of the concerns over adding deck slots is that it could complicate the UI for Hearthstone and make it more difficult for new players to understand. I have what I think would be a good solution to add more deck slots without increasing the learning curve for the game much if at all. First I would take a look at the current selection screen for starting to play a game. It defaults to showing the decks that are custom built by the player if they have any custom decks, and there is an option to page over to the basic decks. This basic deck screen is perfect for how I would change this process. Instead of having 2 pages of decks, 1 for basic and 1 for custom, you would just see the select a Hero screen. Then once you selected the Hero you wanted, you would see all of the decks that