6
votes

I'm converting an Angular app to use TypeScript, but this is a general TypeScript question, not about Angular. The angular js files are along the lines:

(function () {
  var app = angular.module('myModule', []);
  app.controller('myController', ['$scope',
    function ($scope) {
      $scope.myNewProperty = "Bob";
    }
  ]);
})();

And I have converted that to lovely TypeScript class syntax:

class myController {
    constructor($scope) {
        $scope.myNewProperty = "Bob";
    }
}

angular.module('myModule', []).controller("myController", myController);

All works fine, except the generated JS is not wrapped according to the JS module pattern (i.e. in an outer anonymous function):

var myController =     (function () {
      app.controller('myController', ['$scope',
        function ($scope) {
          $scope.myNewProperty = "Bob";
        }
      ]);
    })();

var app = angular.module('myModule', []);

So myController is now global. If I put the class in a TypeScript module, then the js generates with the module name as a global variable:

var TsModule;
(function (TsModule) {
    var myController =     (function () {
          app.controller('myController', ['$scope',
            function ($scope) {
              $scope.myNewProperty = "Bob";
            }
          ]);
        })();

    var app = angular.module('myModule', []);
})(TsModule || (TsModule = {}));

How do I stop TypeScript polluting the global scope in this way? I just want it all wrapped in a nice local scope. I have seen it said elsewhere "just go back to the old JS syntax" (i.e. no TypeScript class). How can I define an AngularJS service using a TypeScript class that doesn't pollute the global scope?

But the whole point of us using TypeScript /is/ the class syntax. Is TypeScript at odds with any (sensible) JS programmer who knows not to go global?

4
Been a while since I used TypeScript so I might be off on this one, but have you tried wrappping the above TypeScript code in an immediately invoked lambda function? (() => { // Code here... })(); - Daniel Edholm Ignat
I get the frustration, but using a single, well defined Module name for all your files is hardly polluting the global scope. It will be creating a single variable on the global scope, and even then, if you aren't exporting your classes, then it will never be more than an empty object. That's a pretty minor issue. - Josh
Just FYI (you might already know) class expressions are coming in TS next which will allow them in IIFE. Also for OP this video (I made) on module patterns in TypeScript might be useful for you : youtube.com/watch?v=KDrWLMUY0R0&hd=1 - basarat
@AluanHaddad - yes, they can now. This wasn't allowed in December 2014. - Fenton

4 Answers

4
votes

You can simply wrap your class inside a module

The module itself will be global, but if you don't export the class, there is little worry about polluting the global scope with a single module name.

So this TypeScript:

module MyModule {
  class MyClass {
     constructor(){}
  }
}

Will produce the following JS:

var MyModule;
(function (MyModule) {
    var MyClass = (function () {
        function MyClass() {
        }
        return MyClass;
    })();
})(MyModule || (MyModule = {}));
2
votes

Quick Update

In the latest versions of TypeScript you can nest a class inside an IIFE:

(() => {
    class Example {

    }
})();

The resulting output is:

(function () {
    var Example = (function () {
        function Example() {
        }
        return Example;
    }());
})();

Original Answer

You can avoid adding to the global scope using a module pattern such as AMD or CommonJS. When you use either of these, each TypeScript file is considered an external module and is kept out of global scope.

This example removes Angular for the purposes of the example, but while RequireJS adds the define method to global, none of your code is placed in this scope.

MyModule.ts

export class myController {
    constructor($scope) {
        $scope.myNewProperty = "Bob";
    }
}

app.ts

import MyModule = require('MyModule');

var controller = new MyModule.myController('');

HTML

<script src="Scripts/require.js" data-main="app.js"></script>

What app.js looks like:

define(["require", "exports", 'MyModule'], function (require, exports, MyModule) {
    var controller = new MyModule.myController('');
});

Alternatively... as you know... you can still implement it all using the JavaScript you are already using if you wish - you'll still get auto-completion and type checking, which are major benefits even if you aren't getting classes.

1
votes

Josh is correct. Use module. As well, it is probably a good idea to use the grunt or gulp uglifyer that has an option to wrap your entire application in a closure at build time.

This is not an answer to your question, but more a suggestion.

On a side note, consider this syntax for controllers

module myModule {

    // We export the class so that we can access its interface in tests.
    // The build time gulp or grunt uglify will wrap our app in a closure
    // so that none of these exports will be available outside the app. 
    export class MyController {

         myNewProperty = "bob";

        // Next we will use Angulars $inject annotation to inject a service
        // into our controller. We will make this private so that it can be 
        // used internally.
        static $inject = ['someService'];
        constructor(private someService: ng.ISomeService) {}

        // we can access our class members using 'this'
        doSomething() {
            this.someService.doSomething(this.myNewProperty);
        }

    }

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

}

Along with this syntax you would use the controllerAs syntax.

0
votes

Just as in JavaScript, you can fully avoid namespace pollution by wrapping your declarations inside an immediately invoked function expression.

So the original JavaScript:

(function () {
  var app = angular.module('myModule', []);
  app.controller('myController', ['$scope',
    function ($scope) {
      $scope.myNewProperty = "Bob";
    }
  ]);
})();

becomes the following TypeScript:

(function () {
  class MyController {
    static $inject = ['$scope'];
    contructor($scope: ng.IScope & { myNewProperty: string }) {
      $scope.myNewProperty = 'Bob';
    }
  }

  angular.module('myModule', [])
    .controller('MyController', MyController);
})();

Note that this introduces no names into the surrounding scope. Actually, the original JavaScript is perfectly valid TypeScript, but it does not take advantage of TypeScript. Also note that I edited slightly for style.

At any rate, if you are not using modules with a module loader such as RequireJS, SystemJS, or what have you, you can still avoid namespace pollution by following the tried and true IIFE pattern. This is my recommendation.