node/typescript: how to ensure imports with side effects are run? - node.js

I am writing a node app in typescript. I have written a class decorator #myDecorator, and the purpose of #myDecorator is such that I need to keep track of all the classes to which it's applied.
My question: how do I make sure all of those decorated classes are loaded before making use of that behavior? Some example code will help to make this more concrete:
Declaration of the decorator in file myDecorator.ts:
type ConstructorType = { new (...args: any[]): any };
// keep track of decorated classes
const registeredClasses: Map<string, ConstructorType> = new Map();
// class decorator
export function myDecorator<T extends ConstructorType>(targetConstructor: T) {
// create the modified class
const newClass = class extends targetConstructor { /* ... */ }
// register the modified class
registeredClasses.set(targetConstructor.name, newClass);
// and return it
return newClass;
}
export function doSomethingWithMyDecoratedClasses() {
//... some behavior that relies on `registeredClasses`
}
Declaration of a decorated class in file someClass.ts
import {myDecorator} from 'myDecorator.ts'
#myDecorator
class SomeClass { /* ... */ }
Making use of doSomethingWithMyDecoratedClasses in anotherFile.ts:
import { doSomethingWithMyDecoratedClasses } from 'myDecorator.ts`
//...
doSomethingWithMyDecoratedClasses()
//...
The problem is that I need to make sure that SomeClass has been added to registeredClasses before I make this call to doSomethingWithMyDecoratedClasses. And, in fact, I've written a number of such classes in my app, and I need to make sure they are all registered.
My current best understanding is that I need to call import 'someClass.ts' in anotherFile.ts (and, in fact, import all files where decorated classes are declared), so really I need to import someClass1.ts, import someClass2.ts, ...
Is that the best/only approach? Is there a recommended pattern for doing so?

Most applications have an index file that is responsible for importing the top level things. If you import doSomethingWithMyDecoratedClasses there, you'll guarantee that everything else is imported first.
An alternative would be to not call it in the root level of a module, and instead wait for an event.

Related

Static data initialised twice due to typo in import in TypeScript

I have a newbie question on TypeScript imports. I tried to make a class which holds some data in a static variable, and the data is lazily initialised in getInstance() method.
myStaticClass.ts:
class MyData {
x = 1;
}
export class MyStaticClass {
private static data: MyData;
static getInstance() {
if (MyStaticClass.data == null) {
console.log('data is null, initialising');
MyStaticClass.data = new MyData();
}
return MyStaticClass.data;
}
}
I imported this class in 2 other classes:
a.ts
import { MyStaticClass } from './MyStaticClass';
// NOTE the typo above - uppercase file name
export class A {
logX() {
console.log(MyStaticClass.getInstance().x);
}
}
index.ts
import { MyStaticClass } from './myStaticClass';
import { A } from './a';
console.log(MyStaticClass.getInstance().x);
new A().logX();
To my surprise, the output of ts-node index.ts is
data is null, initialising
1
data is null, initialising
1
If I correct the import the output is as expected - data is initialised only once.
I also checked that I get one initialisation for one variant of spelling (added 3rd class with another letter in upperCase)
Can anyone explain why this behaviour is in place?
(Additionally, what tools / debug statements I could have used to identify what is happening?)
Can I force TypeScript to flag this as error?
I am on MacOs, TS 3.6.3, node-ts 8.4.1
While on Windows two differently cased file names always point to the same file, on other platforms they do not. This means that imports with different file names are treated as different modules, this is by design and is not considered an issue (see here for node discussion)
The simple solution is to force consistent file casing to be used when importing modules. Typescript does have a compiler option to force this named forceConsistentCasingInFileNames (See docs). This option should prevent such issues.

How do I improve this object design in Typescript?

I have created a class in Typescript that implements a simple stream (FRP). Now I want to extend it with client side functionality (streams of events). To illustrate my problem, here is some pseudo-code:
class Stream<T> {
map<U>(f: (value: T) => U): Stream<U> {
// Creates a new Stream instance that maps the values.
}
// Quite a few other functions that return new instances.
}
This class can be used both on the server and on the client. For the client side, I created a class that extends this one:
class ClientStream<T> extends Stream<T> {
watch(events: string, selector: string): Stream<Event> {
// Creates a new ClientStream instance
}
}
Now the ClientStream class knows about map but the Stream class doesn't know about watch. To circumvent this, functions call a factory method.
protected create<U>(.....): Stream<U> {
return new Stream<U>(.....)
}
The ClientStream class overrides this function to return ClientStream instances. However, the compiler complains that ClientStream.map returns a Stream, not a ClientStream. That can be 'solved' using a cast, but besides being ugly it prevents chaining.
Example code that exhibits this problem:
class Stream {
protected create(): Stream {
return new Stream()
}
map() {
return this.create()
}
}
class ClientStream extends Stream {
protected create(): ClientStream {
return new ClientStream()
}
watch() {
return this.create()
}
}
let s = new ClientStream().map().watch()
This does not compile because according to the compiler, the stream returned from map is not a ClientStream: error TS2339: Property 'watch' does not exist on type 'Stream'.
I don't really like this pattern, but I have no other solution that is more elegant. Things I've thought about:
Use composition (decorator). Not really an option given the number of methods I would have to proxy through. And I want to be able to add methods to Stream later without having to worry about ClientStream.
Mix Stream into ClientStream. More or less the same problem, ClientStream has to know the signatures of the functions that are going to be mixed in (or not? Please tell).
Merge these classes into one. This is a last resort, the watch function has no business being on the server.
Do you have a better (more elegant) solution? If you have an idea that gets closer to a more functional style, I'd be happy to hear about it. Thanks!
What you're trying to do is called F-bounded polymorphism.
In TypeScript this is done via the this keyword. Take a look at Typescript's documentation for polymorphic this types. If you follow the documentation, you should be able to implement what you want :-)
Actually, just make sure that you're returning this in your member methods and you should be fine!

Difference between `import from` and `import require` in TypeScript

I use node.js and I recently decided to give TypeScript a shot, But I'm kinda confused on how modules get imported. I see two different syntax that I couldn't find out what's their difference exactly:
import * as a from 'a'; // ES6 standard to import stuff
// OR ...
import a = require('a');
Are these the same thing? and if they're not, where should I use each one of them?
import * as a from 'a'; is the new "ES6 style" import syntax (available since Typescript 1.5).
Whenever possible, this syntax should now be used.
There is one caveat though. The ES6 import syntax can only import modules (as defined by ES6) or objects (classes, interfaces, vars,... ) exported as part of a module.
Some Javascript librairies will directly export a function or class, and the corresponding definition file will typically look like this:
declare module "my-class" {
class MyClass { ... }
export = MyClass
}
In this case, the "old" import syntax is the only one that can be used
import MyClass = require("my-class");
Failure to use this syntax will result in error TS2497
Check this issue for details and a possible workaround which would be, in the previous case, to add an empty module declaration to the definition file
declare module "my-class" {
class MyClass { ... }
module MyClass {} // <=
export = MyClass
}

Typescript + requirejs: How to handle circular dependencies?

I am in the process of porting my JS+requirejs code to typescript+requirejs.
One scenario I haven't found how to handle is circular dependencies.
Require.js returns undefined on modules that are also dependent on the current and to solve this problem you can do:
MyClass.js
define(["Modules/dataModel"], function(dataModel){
return function(){
dataModel = require("Modules/dataModel");
...
}
});
Now in typescript, I have:
MyClass.ts
import dataModel = require("Modules/dataModel");
class MyClass {
dataModel: any;
constructor(){
this.dataModel = require("Modules/dataModel"); // <- this kind of works but I lose typechecking
...
}
}
How to call require a second time and yet keep the type checking benefits of typescript? dataModel is a module { ... }
Specify the type using what you get from import i.e
import dataModelType = require("Modules/dataModel");
class MyClass {
dataModel: typeof dataModelType;

What's the correct way to use requireJS with typescript?

The examples I have found here and here say to use module(). However, when I compile I get "warning TS7021: 'module(...)' is deprecated. Use 'require(...)' instead."
So a couple of basic questions:
When using typescript and requireJS, how do I access a class in one
.ts file from another .ts file where requireJS will load the second
file and give me the class in the first file?
Is there a way to do the standard requireJS approach with two .ts files where the define() at the top loads the second ts file and returns back the object it builds at the end?
Sort-of the same as question #2. From a java script file, can I use the define() construct on a type script file to get the instantiated object? If so, how?
Update: The following gives me a tsc compile error:
///<reference path='../../libs/ExtJS-4.2.0.d.ts' />
///<reference path='../../libs/require.d.ts' />
import fdm = require("./file-definitions");
require(["../../scripts/ribbon"], function () {
export module Menu {
export class MainMenu {
I would have commented on David's reply to basarat's answer (regarding modules and classes), but I don't have the reputation. I know this question is stale, but I didn't find an answer elsewhere.
I succeeded by using basarat's videos, combined with some other resources, to figure it out for classes like David Thielen needed. Note that I no longer have entries for my ts source files, but I do have amd-dependency and import statements. In Eclipse with palantir's TS plugin, my code completion and ability to jump from usage to definition is working with just the amd-dependency and import statements. The header files still need statements since they have nothing to do with deployment and are only used by the TS compiler. Note also that the .ts file extensions are used for reference statements but not the amd and import statements.
In Utils.ts I have:
///<reference path="headers/require.d.ts" />
export function getTime(){
var now = new Date();
return now.getHours()+":"+now.getMinutes()+':'+now.getSeconds();
}
In OntologyRenderScaler I have:
///<reference path="headers/require.d.ts" />
///<reference path="headers/d3.d.ts" />
///<reference path="headers/jquery.d.ts" />
///<amd-dependency path="Utils" />
import Utils = require('./Utils');
export class OntologyRenderScaler {
...
Utils.getTime();
...
}
In OntologyMappingOverview.ts I have:
///<reference path="headers/require.d.ts" />
///<reference path="headers/d3.d.ts" />
///<reference path="headers/jquery.d.ts" />
///<amd-dependency path="Utils" />
///<amd-dependency path="OntologyGraph" />
///<amd-dependency path="OntologyFilterSliders" />
///<amd-dependency path="FetchFromApi" />
///<amd-dependency path="OntologyRenderScaler" />
///<amd-dependency path="GraphView" />
///<amd-dependency path="JQueryExtension" />
import Utils = require('./Utils');
import OntologyGraph = require('./OntologyGraph');
import OntologyRenderScaler = require('./OntologyRenderScaler');
import OntologyFilterSliders = require('./OntologyFilterSliders');
import GraphView = require('./GraphView');
export class OntologyMappingOverview extends GraphView.BaseGraphView implements GraphView.GraphView {
ontologyGraph: OntologyGraph.OntologyGraph;
renderScaler: OntologyRenderScaler.OntologyRenderScaler;
filterSliders: OntologyFilterSliders.MappingRangeSliders;
...
this.renderScaler = new OntologyRenderScaler.OntologyRenderScaler(this.vis);
...
}
I didn't manage (yet!) to get things working like codeBelt suggested above, but an exchange we had on his blog revealed that if I get his approach working (with export MyClass at the bottom of the file), then I would not need to double up the imported identifer with the class name. I suppose it would export the class of interest rather than the namespace it is defined in (the implicit external module, i.e. the TypeScript file name).
For :
When using typescript and requireJS, how do I access a class in one
.ts file from another .ts file where requireJS will load the second
file and give me the class in the first file? Is there a way to do the
standard requireJS approach with two .ts files where the define() at
the top loads the second ts file and returns back the object it builds
at the end?
simply :
// from file a.ts
export class Foo{
}
// from file b.ts
// import
import aFile = require('a')
// use:
var bar = new aFile.Foo();
and compile both files with --module amd flag.
For :
Sort-of the same as question #2. From a java script file, can I use
the define() construct on a type script file to get the instantiated
object? If so, how?
To use a.ts from b.js simply :
// import as a dependency:
define(["require", "exports", 'a'], function(require, exports, aFile) {
// use:
var bar = new aFile.Foo();
});
This is similar to what you would get if you compile b.ts
You want the export statement below the class you are creating.
// Base.ts
class Base {
constructor() {
}
public createChildren():void {
}
}
export = Base;
Then to import and use into another class you would do:
// TestApp.ts
import Base = require("view/Base");
class TestApp extends Base {
private _title:string = 'TypeScript AMD Boilerplate';
constructor() {
super();
}
public createChildren():void {
}
}
export = TestApp;
I have been playing with typescript, trying to integrate it in our existing javascript/requirejs project.
As setup, I have Visual Studio 2013 with Typescript for vs v 0.9.1.1. Typescript is configured (in visual studio) to compile modules in amd format.
This is what I have found works for me (there might be a better way of course)
Use amd-dependency to tell the typescript compiler adds the required module to the list of components which must be loaded
In the constructor of the class being exported, use requirejs’s require function to actually fetch the imported module (at this point this is synchronous because of the previous step). To do this you must reference require.d.ts
As a side note, but since it is in my view essential to typescript, and because it gave me a bit of a headache, in the example I show two ways to export classes which use interfaces. The problem with interfaces is that they are used for type checking, but they produce no real output (the generated .js file is empty), and it causes problems of the type ‘’export of a private class”
I have found 2 ways of exporting classes which implement an interface:
Simply add an amd-dependency to the interface (as is in the Logger.ts file)
And export a typed variable holding a new instance of the class
The exported class can be consumed directly (ie myClass.log(‘hello’));
Don’t add the amd- dependency to the interface (as is in the Import.ts file)
And export a function (ie Instantiate()) which returns a variable of type any holding a new instance of the class
The exported class can be consumed via this function (ie myClass.instantiate().log(‘hello’))
It seems like the first option is better: you don’t need to call the instantiate function, plus you get a typed class to work with. The downside is that the [empty] interface javascript file does travel to the browser (but it’s cached there anyway, and maybe you are even using minification in which case this does not matter at all).
In the next blocks of code there are 2 typescript modules loaded with requires (amd): Logger and Import.
ILogger.ts file
interface ILogger {
log(what: string): void;
}
Logger.ts file
///<reference path="./ILogger.ts"/>
//this dependency required, otherwise compiler complaints of private type being exported
///<amd-dependency path="./ILogger"/>
class Logger implements ILogger {
formatOutput = function (text) {
return new Date() + '.' + new Date().getMilliseconds() + ': ' + text;
};
log = function (what) {
try {
window.console.log(this.formatOutput(what));
} catch (e) {
;
}
};
}
//this approach requires the amd-dependency above for the interafce
var exportLogger: ILogger = new Logger();
export = exportLogger;
Using Logger.ts in another ts file(Import.ts)
///<reference path="../../../ext/definitions/require.d.ts"/>
///<amd-dependency path="Shared/Logger"/>
///<amd-dependency path="./IImport"/>
class _Import implements IImport{
ko: any;
loggerClass: ILogger;
constructor() {
this.ko = require('knockout');//require coming from require.d.ts (in external_references.ts)
this.loggerClass = require('Shared/Logger');
}
init(vm: any) {
this.loggerClass.log('UMF import instantiated...');
}
}
////Alternative Approach:
////this approach does not require adding ///<amd-dependency path="./IImport"/>
////this can be consumed as <imported_module_name>.instantiate().init();
//export function instantiate() {
// var r : any = new _Import();// :any required to get around the private type export error
// return r;
//}
//this module can be consumed as <imported_module_name>.init();
var exported: IImport = new _Import();
export = exported;
IImport.ts file
interface IImport {
init(vm: any): void;
}
To consume the Import module straight from javascript use something like (sorry I have not tried this one, but it should work)
define (['Import'], function (import)
{
//approach 1
import.init();
////approach 2
//import.instantiate().init();
});

Resources