M. Bischof
M. Bischof

Reputation: 31

Import classes from external typescript library which uses namespaces

I have an existing Angular 2 Project and now I have to use an external TypeScript Library which uses Namespaces. I somehow need to import some classes from the External library so I can use them.

externalLibrary.ts:

namespace com.foo.bar {

  export class A {
    doSomething() {
    }
  }
}

And in My Angular2 Component I want to to do:

myA: A = new A();
myA.doSomething(); 

So far, i have tried:

1)

import {A} from '../dir/externalLibrary';

which results in a compilation error because it says: 'externalLibrary.ts is not a module'.

2)

/// <reference path='../dir/externalLibrary.ts'>
...
myA: com.foo.bar.A = new com.foo.bar.A();

which results in a run-time error saying: 'ReferenceError: com is not defined'

My Question is: How can I use the class A in my Project? I am thankful for any help.

EDIT 1:

Just for clarification: The external library is a Java Library which is then transpiled into TypeScript. It consists of ~80.000 lines of code, therefore I can not change too much, because it would take too much time. This is also the reason why I have to stick to namespaces because the Typescipt transpiler creates them out of the existing java package structure.

@Aaron: Thanks for the answer but my upcoming problem with the 'export namespace' approach is as follows: If i put 'export' before a namespace other Classes can not be found any longer:

b.ts:

export namespace com.foo.bar 
{
    export interface B {

    }
}

a.ts

export namespace com.foo.bar {

    export class A implements B
    {

    }
}

In file a.ts the compiler says: 'cannot find name B'. I also tried to somehow import B but I failed. Is there any posibillity to import B or am I completely on the wrong way?

Upvotes: 1

Views: 2477

Answers (2)

Aaron Beall
Aaron Beall

Reputation: 52193

which results in a compilation error because it says: 'externalLibrary.ts is not a module'.

This compile error appears to be the truth. Looking at your definition it isn't exporting a module, it's just declaring a global namespace. You can use an import alias (TS feature, not ES6) to clean up your usage a bit:

import A = com.foo.bar.A;
new A();
/// <reference path='../dir/externalLibrary.ts'>`
myA: com.foo.bar.A = new com.foo.bar.A();

which results in a run-time error saying: 'ReferenceError: com is not defined'

This means you need to actually include the compiled externalLibrary.js in your running code. That could be as simple as a <script> tag, or if you're using a bundler you might be able to import the file as a side-effect: import "./dir/externalLibrary";

If you control the external library (not clear to me if you do) then you can export the namespace:

export namespace com.foo.bar { ... }

Which turns it into a module (that exports a namespace), and you can import the top level namespace:

import { com } from "../dir/externalLibrary";
const a: com.foo.bar.A = new com.foo.bar.A();

You can't import A directly, but you can still use an import alias:

import { com } from "../dir/externalLibrary";
import A = com.foo.bar.A;
const a: A = new A();

But at this point I would say the namespace is pointless. Just export the class from your module:

export class A { ... }

Now you can import it directly and use it:

import { A } from "../dir/externalLibrary";
const a = new A();
a.doSomething();

Edit

Once you turn a file into a module (by adding a top-level export) then only exported members are visible outside the file, and they must be explicitly imported by other files to be used.

You've updated your question with this example that you previously had:

// a.ts
namespace com.foo.bar {
    export class A implements B { }
}
// b.ts
namespace com.foo.bar {
   export class B { }
}

In this case what it was doing is merging namespaces com.foo.bar in global scope. However, once you make these files modules (by adding export namespace) then they are no longer in global scope and are unaware of each other unless they explicitly import each other, so A implements B in the above doesn't know what B is anymore.

You need to import B. But with modules, you can't merge an imported name with a local declared name, so my previous example importing {com} won't work (nor does it make sense when you think of module files as isolated scopes, which is what they are):

// a.ts
import { com } from "./b"; // import "com"
export namespace com.foo.bar { } // declare "com" -- Error: name conflict

You could import the other file using a * as wildcard (basically an ES6 namespace import) to avoid this collision:

// a.ts
import * as b from "./b";
import B = b.com.foo.bar.B; // alias now starts with `b.`
export namespace com.foo.bar {
    export class A implements B { }
}

I'm just going to say right now that the format this code is generated in is outdated and causing friction with ES6 modules. Using namespaces in an ES6 module is considered mostly anachronistic -- before ES6 module files existed, which themselves define scopes, namespaces (in ES5 just global objects/function closures) were a way to define scope. With ES6 modules what you really want is for your file structure to be enough, so com/foo/bar/a.ts is itself the scope, and when you export class A from that file, you are not conflicting with anything, even if A is exported by another file, that other file is a different scope. Consumer files will import by the file path import { A } from "./com/bar/a" (or whatever the relative path is), and if necessary to avoid name conflicts with other A imports it can be handled by that importing file using as.

That said, I understand refactoring your code generation to export idiomatic ES6 modules without namespaces might not be feasible. I would say you have two options:

  1. Go back to generating code using global namespaces (no export namespace) and resolve your build issue so those files are actually included in the build. You haven't mentioned your method here (only that you got a runtime ReferenceError, indicating the file is not being included in your setup), but if you're able to use ES6 import/export I assume you're using a module bundler like Webpack or Rollup. Module bundlers are designed to bundle modules so including some global code can be surprisingly non-trivial. Remember that just because TypeScript can compile it, doesn't mean your bundler knows how to bundle it.

  2. Modify the code generation to export the namespaces, and import in all files using * as wildcards, as in my last example.

Upvotes: 2

Luca Taccagni
Luca Taccagni

Reputation: 1059

you should export your namespace too:

export namespace com.foo.bar {

  export class A {
    doSomething() {
    }
  }
}

and then:

import * as comFooBar from '../dir/externalLibrary';

Upvotes: 1

Related Questions