Avoiding relative paths in Angular CLI

AngularTypescriptWebpackAngular Cli

Angular Problem Overview


I'm using the latest Angular CLI, and I've created a custom components folder which is a collection of all components.

For example, TextInputComponent has a TextInputConfiguration class which is placed inside src/components/configurations.ts, and in src/app/home/addnewuser/add.user.component.ts where I use it there is:

import {TextInputConfiguration} from "../../../components/configurations";

This is fine but as my app gets larger and deeper the ../ increases, how do I handle this?

Previously, for SystemJS, I would configure the path through system.config.js as below:

System.config({
..
 map : {'ng_custom_widgets':'components' },
 packages : {'ng_custom_widgets':{main:'configurations.ts', defaultExtension: 'ts'},
)};

How do I produce the same for webpack using Angular CLI?

Angular Solutions


Solution 1 - Angular

Per this comment, you can add your application source via paths in tsconfig.json:

{
  "compilerOptions": {
    ...,  
    "baseUrl": ".",
    "paths": {
      ...,
      "@app/*": ["app/*"],
      "@components/*": ["components/*"]
    }
  }
}

Then you can import absolutely from app/ or components/ instead of relative to the current file:

import {TextInputConfiguration} from "@components/configurations";

Note: baseUrl must be specified if paths is.

See also

Solution 2 - Angular

Thanks to jonrsharpe's answer for pointing me in right direction. Although, after adding the paths, as defined in answer, I was still not able to make it work. For anyone else facing same problem as me in future, here's what I did to fix the issues.

I have a shared module and its services are being used in multiple components, so...

tsconfig.json:

{
    "compilerOptions": {
        ...
        "baseUrl": ".", //had to add this too
        "paths": {
            "@shared/*": ["src/app/modules/shared/*"]
        }
    }
}

After this, VS Code was able to resolve the import but I still got following error from webpack while compilation.

> Module not found: Error: Can't resolve

To fix this I had to add

  1. baseUrl of tsconfig in webpack's resolve.modules
  2. paths of tsconfig in webpack's resolve.alias

webpack.config.js:

resolve: {
  extensions: ['*', '.js', '.ts'],
  modules: [
    rootDir,
    path.join(rootDir, 'node_modules')
  ],
  alias: {
    '@shared': 'src/app/modules/shared'
  }
},

component.ts:

import { FooService } from '@shared/services/foo.service'
import { BarService } from '@shared/services/bar.service'
import { BazService } from '@shared/services/baz.service'

To make it even more cleaner, I added an index.d.ts inside services folder and exported all my services from there, like this:

index.d.ts:

export * from './foo.service';
export * from './bar.service';
export * from './baz.service';

and now inside any component:

import { FooService, BarService, BazService } from '@shared/services';

Solution 3 - Angular

Above all answer correct, but after struggling by searching over internet n trying to understand what exactly problem and trying different troubleshooting option, I came to know baseUrl and Path how works toghether

If you use baseUrl:"." like below it works in VScode but not while compiling

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": ".",
    "paths": {
      "@myproject/*": ["src/app/*"]
    }    
}

As per my understanding and my working app and checked in angular aio code, I suggest use as baseUrl:"src" like below

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": "src",
    "paths": {
      "@myproject/*": ["app/*"],
      "testing/*": ["testing/*"]
    }    
}

By having base url as source(src directory), compiler properly resolves modules.

I hope this helps to people resolve this kind of issue.

Solution 4 - Angular

Not sure why but when I tried the other answers in VS2017, I was able to compile Angular without errors but I was still seeing errors in VS "Cannot find Module ...". When I set the baseUrl to "src" from "." everyone was happy.

tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "baseUrl": "src",                 // Main source directory same level as tsconfig
    "paths": {
      "app/*": [ "app/*" ],           // src/app
      "ui/*": [ "ui/*" ],             // src/ui       
      "services/*": [ "services/*" ], // src/services
      "assests/*": [ "assests/*" ],     // src/assests
      "models/*": [ "models/*" ]      // src/models
    },
    "lib": [
      "es2017",
      "dom"
    ]
  }
}

Then to import:

import { AppMenuComponent } from 'ui/app-menu/app-menu.component';

Note: If Visual Studio is still throwing errors try either closing and reopening the file or restarting Visual Studio to get it to recognize the new paths.

Solution 5 - Angular

In Angular 8, no need for the *. The * will cause error of Cannot find module add this to you tsconfig.json file

"baseUrl": "./",
"paths": {
      "@test": [ "src/app/test/" ],
      "@somthing": [ "src/app/something/" ],
      "@name": [ "src/app/name/" ]
    },

Solution 6 - Angular

Keeping it simple

Let's keep this simple with just a few things you need to know to be able to apply this solution to any future project.

Preface: Angular doesn't set up the "usual" @/ or ~/ path mappings, probably because it chose to be less opinionated on this matter.

It does however set a baseUrl for all imports: ./ (which means the folder that tsconfig.json is located in).

Which then means that any non-relative imports (those that don't start with ./ or ../) are automatically prefixed by the baseUrl.

So as long as you use paths that start at this base URL for your imports, you should already have a solution! You shouldn't have problems with refactoring as long as your refactoring tool recognizes the base URL and is able to correctly apply import path refactoring.

So by default, with an unmodified TS config, your can use this kind of import and not worry about relative paths becoming a headache to manage:

// sample import taking into consideration that there is
// a baseUrl set up for all imports which get prefixed to
// all imports automatically:

import { ComingSoonComponentModule } from 'src/app/components/coming-soon/coming-soon.module';

Another solution

But if you really want to use something like a @/ prefix, you can just as easily do that with a simple path mapping, like this:

// tsconfig.json

{
  // ...
  "compilerOptions": {
    "baseUrl": "./",
    // notice that the mapping below is relative to the baseUrl: `src/app/*` is in fact `{baseUrl}src/app/*`, ie. `./src/app/*`.
    "paths": { "@/*": ["src/app/*"] },
    // ...
  },
  // ...
}

Then you can import as either one of these:

import { ComingSoonComponentModule } from 'src/app/components/coming-soon/coming-soon.module';
import { ComingSoonComponentModule } from '@/components/coming-soon/coming-soon.module';
Things to remember:
  1. paths tsconfig option is relative to baseUrl option (so keep in mind and use relative paths for the paths keys).
  2. * - the star is the specified module path (as per your specification in code, where you import), everything else is a prefix or suffix to be added, as per your specification in tsconfig, to the final path.

Solution 7 - Angular

Ive got a MUCH simpler solution to this problem. In your "tsconfig.json" simply set the baseURL to an empty string:

"baseUrl": ""

Now you can reference paths from one file to another from your root like so:

import { ProductService } from 'src/app/services/product.service'; 

These are absolute paths but the way angular uses them they appear to be relative. But they work! I'm not a fan of the "path" shortcuts mentioned above as I like to know where everything is physically located and that's how and why absolute paths were invented.

Keep in mind this fixes the path issues when referencing modules and components on import or when building your Angular project, but that doesn't fix the path problems related to templates, style paths, or assets like images which use a completely different path system in Angular when you build and compile your project.

If this helps explain this goofy path system, the "angular.json" workspace file in the project root controls what the "sourceRoot" folder is, which is set by default to "src". So that is the start of your "source root" folder used by the Angular build piece. Keep in mind the Angular "workspace root" is the project folder above "src" and is the true root of the project and the compiler. So when you write "src/app/..." the root is really above "src". The angular.json apparently derives its starting folder from its current location so "src/app/" is really a relative local folder based on angular.json's location. That is why "/src/app" will not work for the baseURL as its relative to angular.json, I believe.

By default this "src" folder for Angular is what all the build paths resolve to when you build/compile your project. I suspect webpack looks at that same "sourceRoot" "src" value as well in angular.json when it resolves.

After a lot of struggle I found by setting the baseURL in tsconfig to "" rather than "." or "./" you can now use these full paths as long as you start with the "src" folder. These types of absolute paths to me make more sense as now you know visually where everything is from the top root down, rather than going up and down trees of files. That's just crazy!

I have no idea why the Angular kids use those goofy relative path systems. These "./" paths are completely useless and make navigating MUCH harder, in both the compiler path resolution system AND the HTML/URL path resolution systems they use. I think if they had taken the time to learn why and when developers used relative paths they might have realized its not very useful in a wide range of environments to assume every file references every other file from its local folder.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPratik KelwalkarView Question on Stackoverflow
Solution 1 - AngularjonrsharpeView Answer on Stackoverflow
Solution 2 - AngularSyed Ali TaqiView Answer on Stackoverflow
Solution 3 - AngularVirajView Answer on Stackoverflow
Solution 4 - AngularAndy BrahamView Answer on Stackoverflow
Solution 5 - AngularMartian.titanView Answer on Stackoverflow
Solution 6 - AngularPaul-SebastianView Answer on Stackoverflow
Solution 7 - AngularStokelyView Answer on Stackoverflow