Angular Modules
Context and Problem Statement
NgModules are containers for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. They can contain components, service providers, and other code files whose scope is defined by the containing NgModule. They can import functionality that is exported from other NgModules, and export selected functionality for use by other NgModules.
– https://angular.io/guide/architecture-modules
In ADR 0002 Define public module in NPM packages we decided to start using barrel files and restrict imports from other “modules”. This solves some of the pain points we have encountered, however all components are still exported in the barrel files since they need to be defined in an Angular Module. This limits the usefulness of the barrel files.
Angular encourages creating many small NgModules, with people advocating for either one Module per feature, or going so far as defining one module per component. In Angular v14 this was made easier with the introduction of standalone components.
Considered Options
- Do nothing - Maintain the status quo and define most components in a single module. This would mean that each component is still essentially global.
- Angular Modules - Add NgModules alongside our barrel files. This allows for proper encapsulation of internal components.
- Standalone Components - provides the benefits of NgModules without most of the additional boilerplate. Is still in preview and is therefore risky to rely on.
Decision Outcome
Chosen option: Angular Modules
Positive Consequences
- Internal components cannot be re-used outside of the feature.
- Modules are required for supporting lazy loading.
- Similar structure as Standalone Components which will allow for easy migration in the future should this be deemed necessary.
Negative Consequences
- Angular error handling for NgModules is awful and provides cryptic errors that are hard to debug.
- Additional boilerplate.
Guidelines
- Aim to export as few components as possible. In many cases you don’t need to export any components at all, and can instead encapsulate the routing within that module which allows for it to be lazy loaded should it be deemed useful.
- Functionality that needs to be shared across all modules should be placed in the
Shared
feature. - Consider creating a
feature/shared
module in case additional functionality needs to be shared between modules such as the Individual Vault and Organization Vault.
Implementation
An example of a feature module would be the reports. We know that reports are used both for the individual users and for organizations.
reports
shared
report-card.component
report-list.component
reports-shared.module.ts
index.ts
reports
breach-report.component
...
reports.module.ts -> depends on reports-shared.module.ts
reports.component.ts
index.ts
organizations
reports
organization-reports.module.ts -> depends on reports-shared.module.ts