Angular - 3 Patterns for Sharing Components, Modules, and Services

Learn three patterns for sharing components, services, and modules.

Angular - 3 Patterns for Sharing Components, Modules, and Services

In any business, creating consistent rules to the various workflows for each department of the business produces efficiency. Teams move faster as processes operate consistently and ensure the guard rails are in place to accomplish a task.

It's sharing these business rules throughout an organization that creates complexity. While I won't look into an organization's branding or design system, I will take a dive into the three patterns of sharing Angular components, services, and modules across an organization.

Goals

List the pros and cons of each pattern suggested.

1. npm package

2. Monorepository

3. Module Federation

npm Package

Likelihood if you're reading this, external npm packages exist as part of your project's development or runtime dependencies. You may experience packages dependent on a major version of Angular that sits idle on a previous major version while no one takes ownership to upgrade.

Pros of npm Packages

Sharing npm packages exists as an industry standard. Different versions of the package allow consuming applications to upgrade.

If the package exports typings of a class, service, component, enum, etc, integrating the package into an application eases the pain of the unknown typings.

If the package exists in an Artifactory registry, teams can add access control prevent unauthorized teams from installing the package.

Cons of npm Packages

Maintenance and ownership delineation falters as duties for maintainers increase or deprioritize. The maintainers focus shifts to their next project. The real dependents of the npm package are the consumers. Teams dependent on the package often wait for updates even in an open contribution model.

Consumers of the package don't know the tech debt incurred from package updates. Ensuring each consumer adequately tests against the upgrades duplicate efforts that may otherwise be handled in the next ability to share components, monorepositories.

Monorepository

Large organizations use this repository strategy to leverage one codebase for all applications. Code can be shared between libraries and applications without the need to install internally developed packages. Citizens of the monorepository contribute and others benefit from their work.

It's a code repository pattern that stores all of an organization's code in one repo doing trunk based development, meaning all changes merge to main branch. This gets into another discussion of feature flagging, but it's beyond the scope of this.

Pros of Monorepository

In the context of Nrwl's Nx, one package.json is used to manage dev and runtime dependencies; therefore, tech debt items incurred do not replicate across dependent projects. Thinking back to npm packages, when packages update, the same tech debt needs to be performed for each project. This includes implementing new features of the package, testing, and even going through the install of the package. The projects of the monorepo can be unit tested one time and teams can understand the necessary changes.

Sharing code across multiple projects becomes easier and receive updates with contributions from the citizenship. Imagine a component library for forms surfaces a bug. If the team responsible for fixing the bug publishes the fix, all dependent projects need to update the package, test, and deploy. Having the form code in a shared library shortens the development life cycle loop for the change without replicating tech debt efforts.

The thing that also drew my attention to a monorepository strategy with Nx is the ability to share code between both frontend and backend services. In order to better validate models in a form and in NestJs, I created a validation package that allows developers to share validations between NestJs and Angular.

Cons of Monorepo

Within Nx, building an application surfaces as the number one complaint. Nx provides the ability to track the graph of project dependencies so it can intelligently detect the projects that need to be linted, tested, or built. Shared libraries that have multiple consumers trigger can lead to long application build times. I will note Nx provides a service to alleviate that pain point with Nx Cloud Cache and incremental builds.

Large code bases leads to long checkout times for git and poor performance. This is due to the large number branches, commits, and files that need to be tracked.

Module Federation

Initially I'd steer teams away from using module federation unless it makes sense for them to break up their user interface into it's supported domains. But this is one pattern used of creating a microfrontend. Module federation allows teams to consume an externally consumable Angular module, component, service, or model via a static javascript url. Utilizing webpack5 ModuleFederationPlugin, teams define the which files to be exported. These files bundle into a javascript file that a shell application requests from an external url e.g. https://my-module-federation-domain.com/remoteEntry.js and loaded a runtime.

Pros of Module Federation

Independent deployments and autonomy for teams developing with Module Federation alleviate the technical debt needs to be incurred by several teams. One team deploys their changes and the consumers immediately (if cache busting works properly) receive the benefits of the upgrade without having to deploy.

Module federation also allows for multiple versions of a shared framework to work side by side. This means the shell application could be on Angular 13 while the module could be on Angular version 12. Should the shell and federated module have shared dependencies, the shared module will only be loaded once.

Cons of Module Federation

To reduce coupling with the shell application, state management creates complexity and performance issues due to having multiple requests to load data. Values shared with the module should be minimal such as an ID, for instance to load a product options component.

Communication patterns for the module and the shell application become more complex as the module loads at runtime. With the federated module loading at runtime, unknown types of data being shared through a service or a CustomEvent using events on the Window object. If going with Window events, create a strategy for namespacing the event names and heavily document what's expected from those events for consumers.

Branding inconsistencies could also be a challenge. When Angular Components are built, the base styles inherited aren't passed along. So the shell becomes responsible for branding elements. This should come to the forefront of any web site design so developers, designers, and owners derive components based on a the design system.

Finally, be aware of cache busting strategies. Webpack5 plugin for module federation bundles the exported files as one static javascript file to consume. Being a static resource existing outside of the shell's control, an update should result in a cache bust so the browser requests the latest file.

Conclusion

Generating an npm package provides more stability as typings can be imported. Access controls to the repository may be applied to its residing registry. However, team members supporting the project can leave and updates to the repo can slip as ownership blurs. On package updates consuming teams all experience the same tech debt and the task replicates throughout the organization.

Monorepos contain all source code in one repository and shares code across multiple projects. When shared libraries change, consuming libraries benefit immediately from the update. The downside is projects become tightly coupled. Access controls are limited as developers retrieve the entire code base.

Module federation in a microfrontend context allows developers to deploy independently. It reduces the build size as libraries load at runtime. Major version dependencies can differ but may affect performance. Once deployed cache busting, loading data, communication pattern, and a common design library should be chosen before choosing module federation.