Angular2 - root relative imports

I have a problem with imports in angular2/typescript. I'd like to use imports with some root like 'app/components/calendar', instead only way I am able to use is something like:

//app/views/order/order-view.ts
import {Calendar} from '../../components/calendar 

where Calendar is defined like:

//app/components/calendar.ts
export class Calendar {
}

and this obviously gets much worse the lower in hierarchy you go, deepest is '../../..' but it is still very bad and brittle. Is there any way how to use paths relative to project root?

I am working in Visual Studio, and relative imports seem to be the only thing that makes VS able to recognize these imports.y


Solution 1:

Answer

As of TypeScript 2.0 you can set tsconfig.json properties baseUrl as following:

{
  "compilerOptions": {
    "baseUrl": "app"
}

Then, you might use your desired manner of importing components, like:

import { Calendar } from 'components/calendar';

Appendix

Fallback paths resolution

An important consideration is that specifying baseUrl option causes TypeScript compilator to:

  1. Look up a path with regards to baseUrl
  2. On failed resolution (module not found), look up a path with regards to moduleResolution option

SystemJS

Since SystemJS is heavily used in Angular development stage, be sure to accordingly config also systemjs.config.js, so that it resolves paths correctly.


Source and further details: https://github.com/Microsoft/TypeScript/issues/5039

Solution 2:

UPDATE

Just don't use this solution. Embrace node module resolution algorithm. Community rests on it, so everything will break apart if you try to do otherwise. Use aliases or some of the other provided solutions.

Short answer There's a way but you shouldn't do it. Set the compilerOption "moduleResolution" to "classic".

Long answer

Are you using tsconfig.json? I assume you are. I've been looking a way to make statements such as import someModule = require ("../../../../someModule" into import someModule=require("src/path/to/someModule"). I found after hours wasted that tsc may use different algorithms for module resolution. I'm using atom and it creates the tsconfig.json with the compilerOption property moduleResolution set to "node" and it uses the shitty (excuse my french) module resolution algorithm of node. I just put "classic" and started working the obvious way.

Solution 3:

Basically, in Angular 6 you can start the import from the top or bottom.

My two options

They are available with default config generated by angular CLI

  1. From the top
    I prefer this way if it's closer from the root of the app

    import { DateService } from "src/app/shared-services/context/date.service";

  2. From the bottom

    import { DateService } from "../../../../../../../shared-services/context/date.service";

Context:

TypeScript config: tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
  }
}


Angular's stack

ng -v

Angular CLI: 6.0.8
Node: 8.9.0
OS: win32 x64
Angular: 6.0.7
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.6.8
@angular-devkit/build-angular     0.6.8
@angular-devkit/build-optimizer   0.6.8
@angular-devkit/core              0.6.8

@angular-devkit/schematics        0.6.8
@angular/cli                      6.0.8
@ngtools/webpack                  6.0.8
@schematics/angular               0.6.8
@schematics/update                0.6.8
rxjs                              6.2.1
typescript                        2.7.2
webpack                           4.8.3

Solution 4:

One way would be to have files that re export and bundle the files with a shorter path.

You could have a components.ts folder in the root of your application with.

export {Calendar} from './components/calendar'
export {*} from './components/map'

And importing it from components

import {Calendar, Map} from '../../components';

This however will is better suited to exporting modules so others can use them, than the way to structure a project.

The alternative would be to forgo the use of import statements and use internal modules instead.

calendar.ts

module Components {
    export class Calendar {}
}

And you will be able to just use it in any of the files in your projects like this.

new Components.Calendar();

Or import it with an alias.

import Calendar = Components.Calendar;
new Calendar();

Solution 5:

Tl;dr: you should now be able to use src as the root and it will "just work".

With Angular 2.0, when you run ng new my-proj, it auto-creates a file my-proj/src/tsconfig.app.json. This file contains a baseUrl line:

"baseUrl": "./",

Thus, you shouldn't have to change anything about your config and you can scope all of your imports rooted on src. Assuming your project structure looks something like:

my-proj/
  README.md
  src/
    app/
      sidebar/
        sidebar.component.ts
      user.service.ts

you can import your files with:

import { Sidebar } from 'app/sidebar/sidebar.component';
import { UserService } from 'app/user.service';

Edited to add: Sublime does not read src/tsconfig.json, though, it only reads the top-level tsconfig.json (see this issue). If you're getting Cannot find module 'app/...' errors on your import lines in Sublime, you can add:

"baseUrl": "./src",

to your top-level tsconfig.json file.