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

  1. Hello , I have been training students on AngularJS for past 6 months, and at times, I have used your blog as reference for the class training and also for my personal project development. It has been so much useful. Thank you, keep writing more:)
    Shashaa
    AngularJS training in Chennai

    ReplyDelete

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') …

Gamify TDD

I like it when things that would not normally be associated with games add concepts from games as a way to incentives you to accomplish things. Why simply go for a run if you can have an app that will track you and give you a gold star if you do better than you did the last time? Why go to the coffee shop that only gives you coffee if the other one will give you points that you can redeem for free drinks eventually?

I was recently introduced to CodeSchool, an online training system similar to PluralSight, it has video courses and challenges you can take to prove that you retained what the video taught. CodeSchool also adds badges and tracks to your learning, so as you complete a video and its challenges you get a badge. Complete a collection of courses within a specific discipline and you become a master of that discipline.

Some of these incentives are not tangible and really don't mean much in the real world, but they tend to work for me. If I start working towards a large goal a…