I am using AngularJS and TypeScript. I want to implement an AngularJS service using a Typescript class, like this:
class HelloService { public getWelcomeMessage():String { return "Hello"; } } angular.module('app.services.helloService', []).factory('helloService', () => { return new HelloService(); });
This compiles to the following javascript code:
var HelloService = (function () { function HelloService() { } HelloService.prototype.getWelcomeMessage = function () { return "Hello"; }; return HelloService; })(); angular.module('app.services.helloService', []).factory('helloService', function () { return new HelloService(); });
This pollutes the global namespace with the variable HelloService, which I obviously don't want. (Using Chrome's console I verified that HelloService was an object.) How can I solve/avoid this problem?
I tried the obvious:
angular.module('app.services.helloService', []).factory('helloService', function () { class HelloService { ...} return new HelloService(); });
but that gives me a compile error ("Unexpected token; 'statement' expected.").
One possible solution I can think of is using TypeScript's import and export somehow, which in turn will use RequireJS. This probably will wrap the HelloService within a define function, thus avoiding pollution of the global scope with HelloService. However, I don't want to use RequireJS in my AngularJS application for now, as I think AngularJS is good enough for my use, and it adds complexity.
So, my question is, how can I define an AngularJS service using a TypeScript class that doesn't pollute the global scope?
Guide to using TypeScript in AngularJS applications. Supporting older AngularJS applications doesn't mean you can't take advantage of modern tools like TypeScript. Not every file in your application needs to be written in TypeScript at once, you just can rename all your JavaScript files to have a . ts extension.
Angular is a modern framework built entirely in TypeScript, and as a result, using TypeScript with Angular provides a seamless experience. The Angular documentation not only supports TypeScript as a first-class citizen, but uses it as its primary language.
First of all, Angular is based on TypeScript while AngularJS is based on JavaScript. TypeScript is a superset of ES6 and it's backward compatible with ES5. Angular has also benefits of ES6 like: lambda operators, iterators or reflection's mechanism. AngularJS uses terms of scope and controller.
The static $inject
array and constructor remain unchanged from the previous example.
The only change is to split the classes into multiple files and use ES6 modules to pull in the class definitions.
/lib/HelloService.ts:
export class HelloService { public getWelcomeMessage():String { return "Hello from HelloService"; } }
/lib/AnotherService.ts:
import {HelloService} from './HelloService'; /** * Service that depends on HelloService. */ export class AnotherService { // Define `HelloService` as a dependency. static $inject = ['HelloService']; constructor( // Add the parameter and type definition. public HelloService: HelloService ){} public getWelcomeMessage():String { // Access the service as: `this.HelloService` // Enjoy auto-completion and type safety :) var helloMsg = this.HelloService.getWelcomeMessage(); return "Welcome from AnotherService, " + helloMsg; } }
/index.ts:
// Using the services. import {HelloService} from './lib/HelloService'; import {AnotherService} from './lib/AnotherService'; angular.module('HelloApp', []) .service('HelloService', HelloService) .service('AnotherService', AnotherService) .run(['AnotherService', function(AnotherService: AnotherService){ console.log(AnotherService.getWelcomeMessage()); }]);
Building from Steve Fenton's answer:
To allow dependency injection, add a static $inject
array on your class.
See the Angular $injector documentation on how the $inject
array works.
The dependencies will be injected into your constructor in the order given by the array (and makes it work with minification).
Dependency Injection Example:
namespace MyModule { /** * Angular Service */ export class HelloService { public getWelcomeMessage():String { return "Hello from HelloService"; } } /** * Service that depends on HelloService. */ export class AnotherService { // Define `HelloService` as a dependency. static $inject = ['HelloService']; constructor( // Add the parameter and type definition. public HelloService: MyModule.HelloService ){} public getWelcomeMessage():String { // Access the service as: `this.HelloService` // Enjoy auto-completion and type safety :) var helloMsg = this.HelloService.getWelcomeMessage(); return "Welcome from AnotherService, " + helloMsg; } } } // Using the services. angular.module('app.services.helloService', []) .service('HelloService', MyModule.HelloService) .service('AnotherService', MyModule.AnotherService) .run(['AnotherService', function(AnotherService: MyModule.AnotherService){ console.log(AnotherService.getWelcomeMessage()); }]);
I should provide what I actually ended doing:
module MyModule { export class HelloService { public getWelcomeMessage():String { return "Hello"; } } angular.module('app.services.helloService', []).factory('helloService', () => { return new HelloService(); }); }
In this way I can use
return new HelloService();
instead of
return new MyModule.HelloService();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With