7

Lets say we have the following 2 external modules in TypeScript:

export module My.Services.Common 
{
    export class Helper 
    {
       //...
    }
}

and

export module My.Services 
{
    export class Connection
    {
        //...
    }    
}

Now in my app.ts I would like to use both Connection and Helper classes. What I want to achieve is similar to the following code in C#:

using My.Services;
using My.Services.Common;

Or at least just

using My.Services;

But it looks like I cant work at the same time with both Helper and Connection. If I write:

import {My} from './services/common/Helper';
import {My} from './services/Connection;

Leads to error "duplicate identifier 'My'". That is logical though. So my question is how can I consume different classes from the same (or nested) modules?

1
  • 1
    See my answer. It might be what you are wanting to accomplish! Commented Jun 20, 2016 at 10:37

3 Answers 3

14

My personal view on this is to break away from what you see in C# or Java (which maps more closely to internal modules) and treat it more like the external modules you are using...

Step 1. Ditch the module keyword. The file is a module already.

Step 2. Provide a non-dotted alias when you import.

Step 3. You can now import everything, '*', or specific classes when you import.

./services/common.ts

export class Helper 
{
   //...
}

./services.ts

export class Connection
{
    //...
}

./app.ts

import * as Services from './services'
import { Connection } from './services/common'

var x = new Services.Helper();

var y = new Connection();

You can import multiple members from a module too:

import { Connection, Example } from './services/common'

var y = new Connection();

var z = new Example();
Sign up to request clarification or add additional context in comments.

14 Comments

but how can one place Connection in the same namespace as Services? As in, what if I want one-class-per-file? I need to aggregate these various classes in some way to show they are in the same module.
@Jefftopia the file is the module, the folder structure provides namespaces. For example, you might use the files list.ts for a list module and dictionary.ts for a dictionary module, and place them in: './system/collections/generic/list' and './system/collections/generic/dictionary' respectively.
But that doesn't scale. At scale, you don't want one file for one module. You might want one file per class, which is the standard approach.
@Jefftopia "one file per class" is a standard approach in C#, but not JavaScript. In JavaScript one module per file is a standard approach - so beware of widening the definition of "standard". You'll also have to substantiate the claim that it doesn't scale - I can't respond to it without making assumptions about what you intended by that statement.
@Fenton Why is one module per file standard in javascript? Because it has to be due to how modules are implemented, or because it's better? I feel like Jefftopia here, having to put everything I want in a container/module into the same file does not scale and worsens the cognitive load having to work like this. There is a reason why other languages has one class/interface/struct/etc. per file, and it's not because of language semantics.
|
12

To add to Steve's good answer, since I've already drawn the ASCII art: The top-level object exported from this module, My, doesn't merge with any other object from other files named My. The only thing the My.Services.Common module achieves is making it more annoying to import Helper, because you have to fully qualify its name even though there's nothing else in there.

What you think you've done:

/-My--------------------\
| /-Services---------\  |
| | /-Common---\     |  |
| | | [Helper] |     |  |
| | \----------/     |  |
| |                  |  |
| | [Connection]     |  |
| \------------------/  |
\-----------------------/

What you've actually done:

/-My---------------\   /-My---------------\ 
| /-Services-----\ |   | /-Services-----\ |
| | /-Common---\ | |   | | [Connection] | |
| | | [Helper] | | |   | \--------------/ |
| | \----------/ | |   \------------------/
| \--------------/ | 
\------------------/

This is like having an organization system in your house where you have a dozen shoeboxes, each with a single smaller box inside it, each with a smaller box inside that one. The nested boxes provide no organizational benefit over the shoeboxes!

3 Comments

Extra points for all that ASCII art. You even did rounded corners!
I don't even understand the point of module. Everywhere I read, people are like saying not to use it. Even worse with the namespace which pollutes the global namespace. I feel like I should just forget those two and only use classes and interfaces without any kind of encapsulation.
namespace doesn't necessarily pollute the global namespace. If you're creating a large-ish ES6 module, you might still use namesapce to organize the objects inside it
2

I have found another approach for tackling this organisation problem.

To go back to ASCII art (ᵔᴥᵔ)

My folder structure is so:

|
|-- myawesomemodule
|         |
|         |-- myawesomeclass.ts
|         |-- myevenmoreawesomeclass.ts
|         |-- myawesomemodule.ts
|
|-- myevenmoreawesomemodule
|         |-- myotherclass.ts
|         |-- myanotherclass.ts
|         |-- myevenmoreawesomemodule.ts
|
index.ts

In myawesomemodule.ts, I do:

export {MyAwesomeClass} from './myawesomeclass'
export {MyEvenMoreAwesomeClass} from './myevenmoreawesomeclass'

Similarly in myevenmoreawesomemodule.ts, I do:

export {MyOtherClass} from './myotherclass'
export {MyAnotherClass} from './myanotherclass'

And finally at the root level in index.ts, I do:

import * as MyAwesomeModule from "./myawesomemodule/myawesomemodule";
import * as MyEvenMoreAwesomeModule from "./myevenmoreawesomemodule/myevenmoreawesomemodule";

export {MyAwesomeModule};
export {MyEvenMoreAwesomeModule};

Now I can ship this module off to a consumer, pack as NPM etc.

From a consumer, I do:

import MyPackage = require("mypackage"); //assuming you did npm pack 

OR

import * as MyPackage from "./path/to/index" //assuming you didn't

Then I refer to my class names so:

let instance = new MyPackage.MyAwesomeModule.MyAwesomeClass();

I find this approach a bit better than exposing all my classes in a big module at the root level.

2 Comments

Thank you very much for this post! I'm also writing an API in TS and couldn't figure out how to export classes the consumer will need without forcing him to load lots of different modules.
I think I like this, and I currently trying if this is a good structure for a project growing to far... One question: I think in all filenames (red writing) you need to remove the .ts?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.