MrCroft MrCroft - 1 month ago 19
TypeScript Question

Cannot import a custom component - Angular2 and TypeScript

I'm trying to import a custom component within my app.ts, but getting the error "TS2307: Cannot find module 'MyComponent'" I've looked it over, couldn't find anything that would help me.

Some people wrote: use "moduleResolution": "classic", but if I do that then I get the TS2307 error for everything else (all angular2/...) except MyComponent.

Others wrote "run tsc --declaration MyComponent.ts" - which does generate a MyComponent.d.ts file (while throwing errors in the console, like "Cannot compile unless the --module flag is provided" - which IS provided in tsconfig as an option - or "Cannot find module 'angular2/common'" for whatever I am importing in MyComponent.ts - but it does generate a .d.ts file), but when trying to compile app.ts it still gives the same TS2307 error. Nohting I've found worked.

This is my project structure:

| /app
| /resources
| /dev
| /ts
| app.ts
| MyComponent.ts
| /dist
| /js
| app.js // transcompiled app.ts
| MyComponent.js // transcompiled MyComponent.ts
| /modules // files copied from the /node_modules/**/ folders (because node_modules is outside versioning and also outside public root (public root is the /app folder)
| es6-shim.js
| angular2-polyfills.js
| system.src.js
| Rx.js
| http.dev.js
| angular2.dev.js
| index.html
|
| /node_modules
| /angular2 // 2.0.0-beta.1
| /es6-promise // 3.0.2
| /es6-shim // 0.33.3
| /rxjs // 5.0.0-beta.0
| /reflect-metadata // 0.1.2
| /systemjs // 0.19.6
| /zone.js // 0.5.10
|


This is my tsconfig.json:

{
"compilerOptions": {
"target": "ES5",
"module": "system",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": true,
"noImplicitAny": false,
"outDir": "app/resources/dist/js"
},
"files": [
"app/resources/dev/ts/app.ts"
]
}


My app.ts :

import { bootstrap } from "angular2/platform/browser";
import { Component, View } from "angular2/core";
import { HTTP_PROVIDERS, Http, Response } from "angular2/http";

//My Custom Components:
import { MyComponent } from 'MyComponent';

/**
* APP
*/
@Component({
selector: 'my-app'
})
@View({
directives: [MyComponent],
template: `
<my-component></my-component>
plus other html
`
})
class MyApp {
public whatever;

constructor(public http: Http) {
// initialize this.whatever
}

makeRequest(): void {
this.http.request('/_http/response.json')
.subscribe((res:Response) => {
this.whatever = res.json();
});
}
}

bootstrap(MyApp, [ HTTP_PROVIDERS ]);


And here's my MyComponent.ts:

import { Component } from 'angular2/core';
import { NgFor, NgIf } from 'angular2/common';

@Component({
selector: 'my-component',
template: `
<div>MY IMPORTED COMPONENT</div>
`
})
export class MyComponent {

constructor() {}

doSomething(): void {
console.log('Doing something');
}
}


My app.ts and MyComponent.ts are both in the same folder. Anyway, I've also tried path like this: import { MyComponent } from "/app/resources/dev/ts/MyComponent".
Also, I've tried adding it to the files array in tsconfig (some people wrote that I should do that)... Still... Nothing worked! I've also checked the js output, because some people wrote that even though it throws the error, it might still be compiled... No luck!

====================

EDIT:



Ok, as inoabrian suggests in the first comment... I thought I tried everything, but it seems I haven't tried "./MyComponent" for the import part.

import { MyComponent } from './MyComponent';


Now the transcompiler does it's job. Thank you inoabrian, but now I have another issue. When I try running in the browser, I check my console and both systemjs and angular2-pollyfills scream:

http://my-app/resources/dist/js/MyComponent 404 (Not Found)


respectively:

XHR error (404 Not Found) loading http://my-app/resources/dist/js/MyComponent(…)


Don't be thrown off by the path (when comparing to my project folder structure), I have my local server pointing at /app when I access http://my-app

This is my index.html with system config and all included js files:

<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... Meta tags & CSS assets -->

<script src="resources/dist/modules/es6-shim.js"></script>
<script src="resources/dist/modules/angular2-polyfills.js"></script>
<script src="resources/dist/modules/system.src.js"></script>
<script src="resources/dist/modules/Rx.js"></script>
<script src="resources/dist/modules/http.dev.js"></script>
<script src="resources/dist/modules/angular2.dev.js"></script>
</head>
<body>
<script>
System.config({
packages: {
app: {
format: 'register',
defaultExtension: 'js'
}
}
});
System.import('resources/dist/js/app.js')
.then(null, console.error.bind(console));
</script>

<my-app></my-app>

</body>
</html>


I have the feeling that I should do one more thing with the transcompiled file "MyComponent.js", but don't know what (I've tried to add it to System.import, hoping it would accept an array... but it seems it doesn't). What should I do next?

P.S. This is disappointing nevertheless, I hoped that importing MyComponent into app.ts would be everything, and that I would find the MyComponent class + everything from MyComponent.ts + my transcompiled app.ts file ALL IN ONE app.js file, not haveing yet another js file :(

Answer

I know it's long overdue, but perhaps others might benefit from an answer. I did realize what the final problem was (other than what I've already edited in the original question). Here it is: I did mention I had a custom folder structure and wasn't willing to change it. If I had, it would have worked by simply following the angular 2 quickstart. But it wouldn't have made me realize the problem in my specific case and I wouldn't have understood how System.js works.

The problem was in my System.config() - in the packages section. This is the bad code:

System.config({
    packages: {
        app: {
            format: 'register',
            defaultExtension: 'js'
        }
    }
});
System.import('my/path/to/app_main_file');

My mistake was thinking that the key "app" is simply a generic key, comming from the word "application". It turns out that in the packages object, each key actually represents a path (where does that package - it's files - reside within your application). So, assuming the folder where my transpiled files are would be "my/app/public" and "app_main_file" is the main js file of my application, then I should've actually had something like this:

System.config({
    packages: {
        'my/app/public': {
            format: 'register',
            defaultExtension: 'js'
        }
    }
});
System.import('my/app/public/app_main_file')
    .then(null, console.error.bind(console));

Or, you can write the config section even nicer, like this:

System.config({
    packages: {
        'my-app': {
            format: 'register',
            defaultExtension: 'js'
        }
    },
    map: {
        'my-app': 'my/app/public'
    }
});

I think it's pretty self-explanatory what "map" does and how to use it.

P.S. I know that in my answer I didn't follow the exact same folder structure as in the original question, but I think it makes more sense to explain it like this. If it matters, just replace "my/app/public" above with what I originally had in the question: "resources/dist/js/", and it's the same thing.

Comments