Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript - she's gotta have it? (where it == global scope)

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?

like image 875
user603563 Avatar asked Dec 10 '14 15:12

user603563


People also ask

Where do I declare global TypeScript?

To declare a global variable in TypeScript, create a . d. ts file and use declare global{} to extend the global object with typings for the necessary properties or methods.

What is global variable in TypeScript?

TypeScript Variable Scope Global Scope − Global variables are declared outside the programming constructs. These variables can be accessed from anywhere within your code. Class Scope − These variables are also called fields. Fields or class variables are declared within the class but outside the methods.

What is JavaScript global scope?

The Javascript global scope is the context where everything in a Javascript program executes by default. This scope includes all variables, objects, and references that are not contained within a customized scope defined by a programmer. Global scope is the entire Javascript execution environment.

What is a local scope JavaScript?

Local Scope occurs when you create a variable inside a function. By doing that, the visibility and accessibility of the variable is only allowed within that function. Any variable created inside the yellow box is a local variable, just like any variable inside blue box is a global one.


Video Answer


3 Answers

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 = {}));
like image 124
Josh Avatar answered Oct 24 '22 04:10

Josh


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.

like image 41
Fenton Avatar answered Oct 24 '22 04:10

Fenton


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.

like image 20
Martin Avatar answered Oct 24 '22 03:10

Martin