Avoiding relative paths in Angular CLI
AngularTypescriptWebpackAngular CliAngular 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
baseUrl of tsconfig
inwebpack's resolve.modules
paths of tsconfig
inwebpack'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:
paths
tsconfig option is relative tobaseUrl
option (so keep in mind and use relative paths for thepaths
keys).*
- 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.