You're probably familiar with imports like that
import { UsersListComponent } from '../../../ui/users-list/users-list.component.ts
And that doesn't look great (actually, the issue with code like that not only about being "beautiful", but also about avoiding (~lowering the amount of) merge requests conflicts
during refactoring
. If you need to implement a big refactoring, implying relocation of files and directories, then usage of relative paths is going to greatly disservice you, creating pre-conditions for merge requests' conflicts). What can be done about it?
We could try to add aliases in tsconfig.json like this
{
"paths": {
"@app/ui": ["src/app/my-module/ui"]
}
}
and combine it with index.ts files (also called "barrels") in the users-list and ui folders
// inside of /users-list folder create an index.ts file and write the following:
export { UsersListComponent } from './users-list.component.ts';
Yes, that way an import could be turned into this
import { UsersListComponent } from '@app/ui';
About barrel files it's important to mention that you should not export them in implicit way, using the asterisk sign (*).
export * from './';
Always prefer explicit export, because implicit export is one of the most prominent factors for circular dependency error (you don't know what exactly you're exporting, especially if there's a big directory, it's easy to miss cross dependency).
In case you have not one, but multiple components, you still can use the same notion, but you have to explicitly
specify all of the components, for instance:
// components/index.ts
export { UsersListComponent } from './users-list/users-list.component.ts';
export { UserProfileComponent } from './user-profile/user-profile.component.ts
...
With setup like that you can use the following imports:
import {
UsersListComponent,
UserProfileFormComponent,
UserProfileRouteComponent,
UserSettingsFormComponent,
UserAvatarComponent,
...
} from '@app/ui';
When c.-d. errors can happen? If you export (using barrels) something with external dependencies. Image you have a folder where there's several different files, like this
folder/region.enum.ts
folder/customer-type.enum.ts
folder/customer.interface.ts
folder/customer.dto.ts
folder/customer.model.ts
If these entities depend only on themselves (for instance, customer interface may import region.enum and customer-type.enum), it's quite safe to export them using barrel file, like this:
folder/index.ts
export { Region } from './region.enum.ts';
export { CustomerType } from './customer-type.enum.ts';
export { CustomerDTO } from './customer.dto.ts';
export { ICustomer } from './customer.interface.ts';
export { Customer } from './customer.model.ts';
Situations that potentially could lead to c.-d. errors can happen if those files have external dependencies or you export them using "asterisk" symbol (*), like this "export * from './'.
So, two main recommendations to avoid c.-d. errors:
- Do not use asterisk sign (
implicit export
). - Be careful with external dependencies (outside of current folder). If your folder has entities with external dependencies (for example components folder), it doesn't mean this feature should not be used, you just have to be careful (you get the most problems from implicit export).
You still can get the error (you just need to import one entity inside another and otherwise), but in most cases they occur when you have several cross-dependable modules.
So, if you have entities in a folder, depending only on each other and you do not use
asterisk notation
, you're safe (usingexplicit export
is the most important part. Again, you can use barrels for components folder if explicitly exported).
I've worked once with nestJS application and was trying to implement barrels there. I was faced with circular dependency error not easy to explain. After reading issues on github I've found out what was the reason behind. If you have a folder ("services") and one entity from that folder imports (= depends on) another entity (another service), than you should NOT use barrel notation here
import { AuthService } from "@app/services";
Instead we should import it the old way:
import { AuthService } from "./auth.service";
(there's more complicated solution that was proposed in one of the issues, implying import rearrangement in index.ts files in such a way that it reflects the structure of dependent relations between them, but I don't think it's a practical solution, especially if you have an huge application).
So, general rule here is if you have multiple entities in a folder with barrel, and there're dependent relations between them, these entities should be imported without barrel notation.
But sometimes you have quite complicated hierarchy, so you cannot use general name "@app/ui" and need something more specific, like "@app/ui/users". There's a way to do it.
Change your tsconfig
into this:
{
...
"@app/ui/*" : "src/app/my-module/ui/*",
...
}
And then import your component like this
import { UsersListComponent } from '@app/ui/users-list';
by using index.ts
file in the users-list folder we don't have to duplicate the same name ('users-list/users-list.component'), and by using the asterisk sign
here (it's ok here, but not in the export) & aliases we're able to specify a particular folder avoiding circular dependency error.
For instance, that's how import would look like for other components from the same module:
import {
UserComponent,
UserProfileFormComponent,
UserType,
IUser,
UserDTO
} from '@app/ui/users';
import {
AvatarComponent,
IAvatar
} from '@app/ui/avatar';
...
One of the most obvious ways to utilize "barrels" (index.ts) files are entities like "util" functions, enums, interfaces, DTOs and so on.
import { Region, Selection, Brands } from "@app/domain/enums"
Final Notation here. Barrels once were present in angular guidelines and then they were removed from it. As I understand it, removal doesn't mean barrels are forbidden or discouraged, they were removed because it's not appropriate place for them (it's not an angular feature). If you look into angular source code, you can easily find heavy usage of barrels, for example just a random angular source code:
https://github.com/angular/angular/blob/master/packages/core/src/di/index.ts
You can also search for "export { } from" in angular repo and find more than 200 references. So it's a feature heavily used inside of the framework.
One just shouldn't use it with asterisk sign ("implicit export") and it's gonna be fine.
Also, absolute paths can be used for styles. Suppose you have a mixins.scss file in src/styles folder. Right now you import them like this
@import "../../../../../mixins.scss";
And you would like to have smth like this
@import "mixins";
That's quite easy to do. In angular.json, architect > builder > options add a new field called
"stylePreprocessorOptions": {
"includePaths": [ "src/styles" ]
}
Here you're specifying a folder
, not a particular file. If you have multiple different entities in that folder, than all of them would be automatically recognised for import. No more relative paths, and in case the path is changed, you'd have to update it only in one place.
Top comments (0)