102

How can I introduce something like 'my-app-name/services' to avoid lines like the following import?

import {XyService} from '../../../services/validation/xy.service';
Thomas
  • 8,357
  • 15
  • 45
  • 81

3 Answers3

147

TypeScript 2.0+

In TypeScript 2.0 you can add a baseUrl property in tsconfig.json:

{
    "compilerOptions": {
        "baseUrl": "."
        // etc...
    },
    // etc...
}

Then you can import everything as if you were in the base directory:

import {XyService} from "services/validation/xy.service";

On top of this, you could add a paths property, which allows you to match a pattern then map it out. For example:

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "services/*": [
                "services/validation/*"
            ]
        }
        // etc...
    },
    // etc...
}

Which would allow you to import it from anywhere like so:

import {XyService} from "services/xy.service";

From there, you will need to configure whatever module loader you are using to support these import names as well. Right now the TypeScript compiler doesn't seem to automatically map these out.

You can read more about this in the github issue. There is also a rootDirs property which is useful when using multiple projects.

Pre TypeScript 2.0 (Still applicable in TS 2.0+)

I've found it can be made easier by using "barrels".

  1. In each folder, create an index.ts file.
  2. In these files, re-export each file within the folder.

Example

In your case, first create a file called my-app-name/services/validation/index.ts. In this file, have the code:

export * from "./xy.service";

Then create a file called my-app-name/services/index.ts and have this code:

export * from "./validation";

Now you can use your service like so (index is implied):

import {XyService} from "../../../services";

And once you have multiple files in there it gets even easier:

import {XyService, MyOtherService, MyOtherSerivce2} from "../../../services";

Having to maintain these extra files is a bit more work upfront (the work can be eliminated using barrel-maintainer), but I've found it pays off in the end with less work. It's much easier to do major directory structure changes and it cuts down on the number of imports you have to do.

Caution

When doing this there's a few things you have to watch for and can't do:

  1. You have to watch for circular re-exports. So if files in two sub-folders reference each other, then you'll need to use the full path.
  2. You shouldn't go back a folder from the same original folder (ex. being in a file in the validation folder and doing import {XyService} from "../validation";). I've found this and the first point can lead to errors of imports not being defined.
  3. Finally you can't have two exports in a sub-folder that have the same name. Usually that isn't an issue though.
David Sherret
  • 101,669
  • 28
  • 188
  • 178
  • I guess this is the way to go but reading the Caution section I think it would be dangerous in larger projects to maintain the re-exports. Did you find a way to avoid relative paths too? If your approach and getting rid of the `../../` can be done together, that would be awesome. I'm wondering if something like `angular2/core` could be possible? – Thomas Jan 21 '16 at 16:12
  • 2
    @ThomasZuberbühler I think in TypeScript 1.8 that will be available ([see here](https://github.com/Microsoft/TypeScript/issues/5039)). – David Sherret Jan 21 '16 at 16:19
  • 3
    How can I download Typescript 2.0+ with npm? – Maxime Dupré May 06 '16 at 19:47
  • @maximedupre You can use `npm install -g typescript@next` as shown here: https://www.npmjs.com/package/typescript – ben Jul 10 '16 at 17:47
  • `npm install -g typescript@beta` does the work as well :) – Vadorequest Aug 09 '16 at 08:50
  • 5
    A small tip - after reading the documentation it turned out that the `baseUrl` is relative to the location of 'tsconfig.json'. So in our case (angular application) the value had to be `"baseUrl": "./app",`, where "app" is the root of the application. – Pawel Gorczynski Nov 16 '16 at 12:42
  • Hi. I'm aware this is an old post. Using ionic2. I can't get this working. "baseUrl": ".", "paths":{ "app.logging" : ["src/app/test/logging/logging"] }. In that file (logging.ts) I have exported all the stuff logging related. Then how should my import look in other places in the app? import { LoggingModule } from 'app.logging' won't do anything – yafrack Dec 23 '16 at 23:40
  • 10
    **angular-cli users only**: if you're using angular-cli 2+, they switched to webpack and blackboxed webpack.config.js (inside node_modules). _"From there, you will need to configure whatever module loader you are using to support these import names as well."_ Since webpack.config.js is blackboxed, you can't do this piece. Luckily, I found the issue was already reported [here](https://github.com/angular/angular-cli/issues/1465) and resolved by [this](https://github.com/angular/angular-cli/pull/2470) PR. **TL;DR** the blackboxed webpack config is smart enough to look at tsconfig.json now. – Kevin Jan 04 '17 at 19:40
  • 1
    for angular-cli users, you can generate a webpack.config with "ng eject". Note that you'll need to remove the ejected:true from .angular-cli.json -> project in order to be able to serve the project. – Drusantia Aug 22 '17 at 07:49
  • @DavidSherret Only way to reduce imports number is to use barrels ? – k11k2 Mar 13 '18 at 14:18
20

Better to use below configuration in tsconfig.json

{
  "compilerOptions": {
    "...": "reduced for brevity",

    "baseUrl": "src",
    "paths": {
      "@app/*": ["app/*"]
    }
  }
}

Traditional way before Angular 6:

`import {XyService} from '../../../services/validation/xy.service';`

should be refactored into these:

import {XyService} from '@app/services/validation/xy.service

Short and sweet!

Pramod Kharade
  • 2,005
  • 1
  • 22
  • 41
Shivang Gupta
  • 3,139
  • 1
  • 25
  • 24
  • this change doesn't work in production @shivangGupta – Mustafa Kunwa Nov 11 '19 at 08:30
  • Hey @Shivang Gupta, what does "..." do in compilerOptions? Sorry, I am new to frontend tech. Or, is this just a symbol you used in your answer to represent other configs already present in tsconfig? – Abhishek Kashyap Feb 19 '21 at 09:58
  • I prefer this method, as having the `@app` clarifies the scope. I do not favor having something like `@core` and `@shared`, `@app/core` and `@app/shared` are just as concise with more context. – Jose Orihuela Nov 03 '21 at 05:56
6

I just came across this question. I know it's way back now but for anyone coming across it there is a simpler answer.

I came across only because something I had been doing for a long time stopped working and I was wondering if something had changed in Angular 7. No, it was just my own code.

Regardless I have only had to change one line in tsconfig.json to avoid long import paths.

{
  "compilerOptions": {
  "...": "simplified for brevity",

   "baseUrl": "src"
  }
}

Example:

// before:
import {XyService} from '../../../services/validation/xy.service';

// after:
import { XyService } from 'app/services/validation/xy.service';

This has worked for me pretty much ever since Angular-CLI came along.

Chris Curnow
  • 643
  • 5
  • 15