Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly import/export classes across angular modules?

This question comes from the context of Enterprise Application.

From all the books I've read and online samples I've seen about angular applications, each time we create a class (component, service, entity, etc.) we export them on the type definition and then directly import them wherever we require a reference (similar to using namespaces on C#) regardless both classes belongs to the same or distinct angular modules.

Ex:

// In 'Commons/logger.service.ts'
export class LoggerService { ... }

// In 'Core/common.service.ts'
export class CommonService { ... }

// In 'Products/' module
import { LoggerService } from '../Commons/logger.service'
import { CommonService } from '../Core/common.service'

export class ProductComponent { ... }

I started working on a (big) enterprise application project and noticed an approach I had never seen before, they created files to gather each type of class (service, entity, method parameter, component) exporting each one of them, export each one of these files on its corresponding angular module file and then, instead of importing the type directly from the type's file, perform the importing from the given module.

The previous example would be transformed to something like this:

// In 'Commons/logger.service.ts'
export class LoggerService { ... }

// In 'Commons/services.ts'
export * from './logger.service.ts'
export * from './other.service.ts'
export * from './another.service.ts'

// In 'Commons/commons.module.ts'
export * from 'services.ts'
export * from 'entities.ts'
/* ... */


// In 'Products/' module
import { LoggerService, OtherService } from '../Commons/commons.module'

export class ProductComponent { ... }

Given that this approach is much more verbose than the one before and that it provokes awkward results in some cases (cyclic reference warning if importing classes within the same module)

My questions are:

  1. Which approach is recommended, from a good design or best practices perspective?
  2. Is this approach recommended over the former? Why? For which cases?
  3. Why this approach isn't introduced on the major sources of documentation (angular online docs, angular books, ...)?
  4. What are the pros and cons of this approach.
like image 207
mdarefull Avatar asked Mar 01 '18 23:03

mdarefull


People also ask

Can you export a module Angular?

Note you can also export Modules. Any Module that imports MyModule will additionally have access to CommonModule . Finally, Services in the providers are exported for you and hence should never be in the exports . Any Module that imports MyModule will have access to MyService .

How do we import the module in Angular?

Importing moduleslink When you use these Angular modules, import them in AppModule , or your feature module as appropriate, and list them in the @NgModule imports array. For example, in the basic application generated by the Angular CLI, BrowserModule is the first import at the top of the AppModule , app. module. ts .

Which modules should be imported for Angular routing?

In Angular, the best practice is to load and configure the router in a separate, top-level module. The router is dedicated to routing and imported by the root AppModule . By convention, the module class name is AppRoutingModule and it belongs in the app-routing.module.ts in the src/app directory.


1 Answers

This is a concept known as a barrel file. These files make importing classes more convenient. It isn't a concept to Angular alone. It seems to be a best practice in TypeScript to use them.

When you have a smaller project, you may not see much of a benefit of creating these files, but they come in to be super helpful when working on larger projects with multiple team members. Below are some of the pros/cons that I'm aware of.

Pro: Less Knowledge Needed Around Module Internals

When a component needs to have a reference to something, it shouldn't have to know what exact file the class, etc. is in. When you don't barrel the stuff within a module then every other file outside of the module will need to know exactly what file (including the sub path) contains that item. If you barrel the items up into a single module barrel, you simply need to know what module it is from. This also gives you the benefit of refactoring a module not requiring you to make sure the path gets updated if the file moves (your tooling may or may not help out with this).

// without barrel files
import { Class1 } from 'app/shared/module1/subpath1/subpath2/class1.service';

// using barrel files
import { Class1 } from 'app/shared/module1';

Pro: Explicit Exporting Outside Module

Another benefit is that a module may contain a bunch of classes, interfaces, etc. that really are only meant to be used within that module. By creating a barrel file for a module, you are letting other devs know what is meant to be used outside of the module. Additionally, you are letting them know exactly what items there are to be used, so they don't need to hunt around to find an interface that they need (again, your tooling may or may not assist in this).

// export these items from module as others need to have references to these
export { ExampleModule } from './example.module';
export { ExampleService } from './example.service';
export { ExampleInterface } from './example.model';

// these also exist in module but others don't need to know about them
// export { ExampleComponent } from './example.component';
// export { Example2Service } from './example2.service';
// export { ExampleInterface2 } from './example.model';

Pro: Cleaner Imports

The final benefit I'll mention is that it helps to clean up your imports. Not only does it make it more clear what items come from what modules, it also helps to make the from portion of your import much shorter as you don't need to traverse down sub directories, etc. As a note, the best practice seems to be to have the barrel file be index.ts in a folder since the TypeScript compiler will look for that file as default when given a folder to import from.

// without barrel files (hopefully they would be not randomly ordered to make it even harder...)
import { Class1 } from 'app/shared/module1/subpath1/subpath2/class1.service';
import { Class2 } from 'app/shared/module1/subpath1/class2.component';
import { Interface1 } from 'app/shared/module1/module1.model';
import { Class3} from 'app/shared/module2/subpath3/class3.service';
import { Interface2 } from 'app/shared/module2/module2.model';

// using barrel files
import { Class1, Class2, Interface1 } from 'app/shared/module1';
import { Class3, Interface2 } from 'app/shared/module2';

Con: Additional File

In trying to identify the cons, the only thing that I can think of is that you are creating another file for each module (or maybe sub folder depending on how you want to do it). It has no run-time or compile-time effect as far as I am aware though.

like image 176
Daniel W Strimpel Avatar answered Oct 07 '22 18:10

Daniel W Strimpel