Enterprise Angular
Enterprise Angular
Manfred Steyer
This book is for sale at http://leanpub.com/enterprise-angular
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Case Studies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Help Improve this Book. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Trainings and Consultancy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Thanks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Literature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Case Studies
This book explores the concepts by using case studies which you can find in my GitHub account:
Thanks
I want to thank several people who have helped me write this book:
• The great people at Nrwl.io⁶ who provide the open-source tool Nx⁷ used in the case studies
here and described in the following chapters.
• Thomas Burleson⁸ who did an excellent job describing the concept of facades. Thomas
contributed to the chapter about tactical design which explores facades.
⁵https://www.softwarearchitekt.at/angular-schulung/
⁶https://nrwl.io/
⁷https://nx.dev/angular
⁸https://twitter.com/thomasburleson?lang=de
Strategic Domain-Driven Design
Monorepos allow large enterprise applications to subdivide into small maintainable libraries. First,
however, we need to define criteria to slice our application into individual parts. We must also
establish rules for communication between them.
In this chapter, I present the techniques I use to subdivide large software systems: strategic design.
It’s part of the domain driven design⁹ (DDD) approach. I also explain how to implement its ideas
with an Nx¹⁰-based monorepo.
To recognise domains, it is worth taking a look at the processes in the system. For example, an e-
Procurement system that handles the procurement of office supplies could support the following
two processes:
We can see that the process steps Approve Order, Request Budget and Approve Budget primarily
revolve around organisational hierarchies and the available budget. Managers are principally
involved here. By contrast, the process step is fundamentally about employees and products.
Of course, we could argue that products are omnipresent in an e-Procurement system. However, a
closer look reveals that the word product denotes different items in some of the process steps shown.
For example, while a product description is very detailed in the catalogue, the approval process only
needs a few key data:
Strategic Domain-Driven Design 5
We must distinguish between these two forms of a product in the ubiquitous language that prevails
within each domain. We create different models that are as concrete and meaningful as possible.
This approach prevents the creation of a single confusing model that attempts to describe everything.
These models also have too many interdependencies that make decoupling and subdividing
impossible.
We can still relate personal views of the product at a logical level. If the same id on both sides
expresses this, it works without technical dependencies.
Thus, each model is valid only within a specific scope. DDD calls this the bounded context¹³. Ideally,
each domain has its own bound context. As the next section shows, however, this goal cannot always
be achieved when integrating third-party systems.
If we proceed with this analysis, we may find the following domains:
¹³https://martinfowler.com/bliki/BoundedContext.html
Strategic Domain-Driven Design 6
If you like the process-oriented approach of identifying different domains alongside the vocabulary
(entities) and groups of domain experts, you might love Event Storming¹⁴. At this workshop, domain
experts analyse business domains.
Context-Mapping
Although the individual domains are as self-contained as possible, they still have to interact
occasionally. In our example, the ordering domain for sending orders could access both the
catalogue domain and a connected ERP system:
A context map determines how these domains interact. In principle, Ordering and Booking could
share the common model elements. In this case, however, we must ensure that modifying one does
not cause inconsistencies.
One domain can easily use the other. In this case, however, it is unclear how much power each
is entitled to. Can the consumer impose specific changes on the provider and insist on backward
compatibility? Or must the consumer be satisfied with what it gets from the provider?
¹⁴https://www.eventstorming.com
Strategic Domain-Driven Design 7
Strategic design defines further strategies for the relationship between consumers and providers.
In our example, Catalog offers an API to prevent changes in the domain from forcibly affecting
consumers. Since order has little impact on the ERP system, it uses an anti-corruption layer (ACR)
for access. If something changes in the ERP system, it only needs an update.
An existing system, like the shown ERP system, usually does not follow the idea of the bounded
context. Instead, it contains several logical and intermingled sub-domains.
Another strategy I want to stress here is Separate Ways. Specific tasks, like calculating VAT, are
separately implemented in several domains:
At first sight, this seems awkward because it leads to code redundancies, breaking the DRY principle
(don’t repeat yourself). Nevertheless, it can come in handy because it prevents dependency on a
shared library. Although preventing redundant code is important, limiting dependencies is vital
because each dependency defines a contract, and contracts are hard to change. Hence, it’s good first
to evaluate whether an additional dependency is truly needed.
As mentioned, each domain should have a bounded context. Our example has an exception: If we
have to respect an existing system like the ERP system, it might contain several bounded contexts
not isolated from each other.
Conclusion
Strategic design is about identifying self-contained (sub-)domains. In each domain, we find ubiqui-
tous language and concepts that only make sense within the domain’s bounded context. A context
map shows how those domains interact.
In the next chapter, we’ll see we can implement those domains with Angular using an Nx¹⁵-based
monorepo.
Learn more about this and further architecture topics regarding Angular and huge enterprise as well
as industrial solution in our advanced Online Workshop¹⁶:
¹⁵https://nx.dev/
¹⁶https://www.angulararchitects.io/schulungen/advanced-angular-enterprise-anwendungen-und-architektur/
Strategic Domain-Driven Design 8
Save your ticket¹⁷ now or request a company workshop¹⁸ for you and your team!
If you like our offer, keep in touch with us so that you don’t miss anything.
For this, you can subscribe to our newsletter¹⁹ and/ or follow the book’s author on Twitter²⁰.
¹⁷https://www.angulararchitects.io/schulungen/advanced-angular-enterprise-anwendungen-und-architektur
¹⁸https://www.angulararchitects.io/schulungen/advanced-angular-enterprise-anwendungen-und-architektur
¹⁹https://www.angulararchitects.io/subscribe/
²⁰https://twitter.com/ManfredSteyer
Implementing Strategic Design with
Nx Monorepos
In the previous chapter, I presented strategic design which allows a software system’s subdivision
into self-contained (sub-)domains. This chapter explores these domains’ implementation with
Angular and an Nx²¹-based monorepo.
If you want to look at the underlying case study²², you can find the source code here²³
I’m following recommendations the Nx team recently described in their free e-book about Monorepo
Patterns²⁴. Before this was available, I used similar strategies. To help establish a common vocabulary
and standard conventions in the community, I am now using the Nx team’s methods.
Implementation with Nx
We use an Nx [Nx]-based workspace to implement the defined architecture. This workspace is an
extension for Angular CLI, which helps to break down a solution into different applications and
libraries. Of course, this is one of several possible approaches. Alternatively, one could implement
each domain as a completely separate solution, a so-called micro-app approach.
The solution shown here puts all applications into one apps folder, while grouping all the reusable
libraries by the respective domain name in the libs folder:
²¹https://nx.dev/
²²https://github.com/manfredsteyer/strategic-design
²³https://github.com/manfredsteyer/strategic-design
²⁴https://go.nrwl.io/angular-enterprise-monorepo-patterns-new-book
Implementing Strategic Design with Nx Monorepos 10
Because such a workspace manages several applications and libraries in a common source code
repository, there is also talk of a monorepo. This pattern is used extensively by Google and Facebook,
among others, and has been the standard for the development of .NET solutions in the Microsoft
ecosystem for about 20 years.
It allows source code sharing between project participants in a particularly simple way and
prevents version conflicts by having only one central node_modules folder with dependencies. This
arrangement ensures that, e.g., each library uses the same Angular version.
To create a new Nx-based Angular CLI project – a so-called workspace –, you can use the following
command:
1 cd e-proc
2 ng generate app ui
3 ng generate lib feature-request-product
Please note the separation between smart and dumb components. Smart components within feature
libraries are use case-specific. An example is a component which enables a product search.
On the contrary, dumb components do not know the current use case. They receive data via
inputs, display it in a specific way, and issue events. Such presentational components “just” help
to implement use cases and hence they are reusable. An example is a date-time picker, which is
unaware of which use case it supports. Hence, it is available within all use cases dealing with dates.
In addition to this, I also use the following categories:
• shell: For an application that has multiple domains, a shell provides the entry point for a
domain
• api: Provides functionalities exposed to other domains
• domain: Domain logic like calculating additional expenses (not used here), validations or
facades for use cases and state management. I will come back to this in the next chapter.
The categories are used as a prefix for the individual library folders, thus helping maintain an
overview. Libraries within the same category are presented next to each other in a sorted overview.
This structure is a fundamental aspect of good software design as it allows splitting into a public
and a private part. Other libraries access the public part, so we have to avoid breaking changes as
this would affect other parts of the system.
However, the private part can be changed at will, as long as the public part stays the same.
If we concentrate on the Catalog domain in our case study, the result is:
Tagging Libraries
The file nx.json defines the tags for our libraries. Nx generates the file:
Implementing Strategic Design with Nx Monorepos 13
1 "projects": {
2 "ui": {
3 "tags": ["scope:app"]
4 },
5 "ui-e2e": {
6 "tags": ["scope:e2e"]
7 },
8 "catalog-shell": {
9 "tags": ["scope:catalog", "type:shell"]
10 },
11 "catalog-feature-request-product": {
12 "tags": ["scope:catalog", "type:feature"]
13 },
14 "catalog-feature-browse-products": {
15 "tags": ["scope:catalog", "type:feature"]
16 },
17 "catalog-api": {
18 "tags": ["scope:catalog", "type:api", "name:catalog-api"]
19 },
20 "catalog-data-access": {
21 "tags": ["scope:catalog", "type:data-access"]
22 },
23 "shared-util-auth": {
24 "tags": ["scope:shared", "type:util"]
25 }
26 }
Alternatively, these tags can be specified when setting up the applications and libraries.
According to a suggestion from the mentioned e-book about Monorepo Patterns²⁷, the domains get
the prefix scope, and the library types receive the prefix kind. Prefixes of this type are intended to
improve readability and can be freely assigned.
²⁷https://go.nrwl.io/angular-enterprise-monorepo-patterns-new-book
Implementing Strategic Design with Nx Monorepos 14
1 "nx-enforce-module-boundaries": [
2 true,
3 {
4 "allow": [],
5 "depConstraints": [
6 { "sourceTag": "scope:app",
7 "onlyDependOnLibsWithTags": ["type:shell"] },
8 { "sourceTag": "scope:catalog",
9 "onlyDependOnLibsWithTags": ["scope:catalog", "scope:shared"] },
10 { "sourceTag": "scope:shared",
11 "onlyDependOnLibsWithTags": ["scope:shared"] },
12 { "sourceTag": "scope:booking",
13 "onlyDependOnLibsWithTags":
14 ["scope:booking", "scope:shared", "name:catalog-api"] },
15
16 { "sourceTag": "type:shell",
17 "onlyDependOnLibsWithTags": ["type:feature", "type:util"] },
18 { "sourceTag": "type:feature",
19 "onlyDependOnLibsWithTags": ["type:data-access", "type:util"] },
20 { "sourceTag": "type:api",
21 "onlyDependOnLibsWithTags": ["type:data-access", "type:util"] },
22 { "sourceTag": "type:util",
23 "onlyDependOnLibsWithTags": ["type:util"] }
24 ]
25 }
26 ]
Development environments such as WebStorm / IntelliJ, or Visual Studio Code show such violations
while typing. In the latter case, you need a corresponding plugin.
Implementing Strategic Design with Nx Monorepos 15
Hint: Consider using Git Hooks, e. g. by leveraging Husky²⁸, which ensures that only code
not violating your linting rules can be pushed to the repository.
Conclusion
Strategic design is a proven way to break an application into self-contained domains. These domains
have a specialised vocabulary which all stakeholders must use consistently.
The CLI extension Nx provides a very elegant way to implement these domains with different
domain-grouped libraries. To restrict access by other domains and to reduce dependencies, it allows
setting access restrictions to individual libraries.
These access restrictions help ensure a loosely coupled system which is easier to maintain as a sole
change only affects a minimum of other parts of the system.
²⁸https://github.com/typicode/husky
Tactical Domain-Driven Design with
Angular and Nx
The previous chapters showed how to use the ideas of strategic design with Angular and Nx. This
chapter builds upon the outlined ideas and describes further steps to respect tactical design too.
The case study used in this chapter is a travel web application which has the following sub-domains:
Using layers is a traditional way of organising a domain. There are alternatives like
hexagonal architectures or clean architecture.
For those aspects that are shared and used across domains, we use an additional shared swimlane.
Shared libraries can be useful. Consider, for example, shared libraries for authentication or logging.
Note: the shared swimlane corresponds to the Shared Kernel proposed by DDD and also
includes technical libraries to share.
As discussed in the previous chapter, access constraints define which libraries can use/depend upon
other libraries. Typically, each layer is only allowed to communicate with underlying layers. Cross-
domain access is allowed only with the shared area. The benefit of using these restrictions is loose
coupling and thus increased maintainability.
To prevent too much logic from being put into the shared area, the approach presented
uses APIs that publish building blocks for other domains. This approach corresponds to
Open Services in DDD.
• As the greyed-out blocks indicate, most util libraries are in the shared area, primarily because
we use aspects such as authentication or logging across systems.
• The same applies to general UI libraries that ensure a system-wide look and feel.
Notice that domain-specific feature libraries, however, are not in the shared area. Feature-related
code should be within its own domain.
While developers may share feature code (between domains), this practice can lead to shared
responsibilities, more coordination effort, and breaking changes. Hence, it should only be shared
sparingly.
Code Organisation
Based on Nrwl.io’s Enterprise MonoRepository Patterns³¹, I distinguish between five categories of
layers or libraries:
This complete architectural matrix is initially overwhelming. But after a brief review, almost all
the developers I’ve worked with agreed that the code organisation facilitates code reuse and future
features.
Of course, we can package these layers in their own libraries. For the sake of simplicity, it is also
possible to store them in a single subdivided library. This subdivision makes sense if these layers are
ordinarily used together and only exchanged for unit tests.
Implementations in a Monorepos
Once we have determined our architecture’s components, we consider how to implement them in
Angular. A common approach by Google is monorepos. Monorepos are a code repository of all the
libraries in a software system.
While a project created with the Angular CLI can nowadays serve as a monorepo, the popular tool
Nx³² offers additional features which are particularly valuable for large enterprises. These include
³²https://nx.dev/
Tactical Domain-Driven Design with Angular and Nx 20
the previously discussed ways to introduce access restrictions between libraries³³. These restrictions
prevent each library from accessing one another, resulting in a highly coupled overall system.
One instruction suffices to create a library in a monorepo:
You use ng generate library instead of ng generate module, requiring no extra effort.
However, you get a cleaner structure, improved maintainability, and less coupling.
The switch directory provided by Nx specifies an optional subdirectory for the libraries, so they
can be grouped by domain:
The libraries’ names reflect the layers. If a layer has multiple libraries, it makes sense to use these
names as a prefix, producing names such as feature-search or feature-edit.
This example divides the domain library into the three further layers mentioned to isolate the exact
domain model:
³³https://www.softwarearchitekt.at/aktuelles/sustainable-angular-architectures-2/
Tactical Domain-Driven Design with Angular and Nx 21
As usual in OO-land, these entities use information hiding to ensure their state remains consistent.
You implement this with private fields and public methods that operate on them.
These entities encapsulate data and business rules. The setStatus method indicates this circum-
stance. DDD defines so-called domain services only in cases where you cannot meaningfully
accommodate business rules in an entity.
DDD frowns upon e ntities that only represent data structures. The community calls them
devaluing bloodless (anaemic)³⁴.
Functional programming splits the previously considered entity model into data and logic parts.
Domain-Driven Design Distilled³⁷ which is one of the standard works for DDD and primarily relies
on OOP, also concedes that this rule change is necessary in the world of FP:
The entities also use public properties here. This practice is common in FP. Excessive use of getters
and setters, which only delegate to private properties, is often ridiculed.
More interesting, however, is how the functional world avoids inconsistent states. The answer is
amazingly simple: Data structures are preferentially immutable. The keyword read-only in the
example shown emphasises this.
Any part of the programme that seeks to change such objects has first to clone it. If other parts of
the programme have first validated an object for their purposes, they can assume that it remains
valid.
Facades
Facades³⁹ (aka applications services) are used to represent the domain logic in a use case-specific
way. They have several advantages:
• Encapsulating complexity
• Taking care of state management
• Simplified APIs
Independent of DDD, this idea has been prevalent in Angular for some time.
For our example, we could create the following facade:
Note the use of RxJS and observables in the facade. The facade can auto-deliver updated flight
information when conditions change. Facades can introduce Redux transparently and @ngrx/store
later when needed, without affecting any external application components.
For the consumer of the facade it is irrelevant whether it manages the state by itself or by
delegating to a state-management library.
Stateless Facades
While it is good practice to make server-side services stateless, this goal is frequently not performant
for services in web/client-tier.
A web SPA has a state, and that’s what makes it user-friendly .
To prevent UX issues, Angular applications avoid repeatedly reloading all the information from the
server. Hence, the facade outlined holds the loaded flights (within the observable discussed before).
Domain Events
Besides performance improvements, using observables provides a further advantage. Observables
allow further decoupling since the sender and receiver do not have to know each other directly.
This structure also perfectly fits DDD, where the use of domain events are now part of the
architecture. If something interesting happens in one part of the application, it sends a domain
event, and other application parts can react.
In the shown example, a domain event could indicate that a passenger is now BOARDED. If this is
relevant for other parts of the system, they can execute specific logic.
For Angular developers familiar with Redux or Ngrx: We can represent domain events as
dispatched actions.
Tactical Domain-Driven Design with Angular and Nx 26
Conclusion
Modern single-page applications (SPAs) are often more than just recipients of data transfer objects
(DTOs). They often have significant domain logic which adds complexity. Ideas from DDD help
developers to manage and scale with the resulting complexity.
A few rule changes are necessary due to the object-functional nature of TypeScript and prevailing
customs. For instance, we usually use immutables and separate data structures from the logics
operating on them.
The implementation outlined here bases upon the following ideas:
• The use of monorepos with multiple libraries grouped by domains helps to build the basic
structure.
• Access restrictions between libraries prevent coupling between domains.
• Facades prepare the domain model for individual use cases and maintain the state.
• If needed, Redux can be used behind the facade without affecting the rest of the application.
If you want to see all these topics in action, check out our Angular architecture
workshop⁴⁰.
⁴⁰https://www.softwarearchitekt.at/schulungen/advanced-angular-enterprise-anwendungen-und-architektur/
From Domains to Microfrontends
Let’s assume you’ve identified the sub-domains for your system. The next question is how to
implement them.
One option is to implement them within a large application – aka a deployment monolith. The
second is to provide a separate application for each domain.
Such applications are called microfrontends.
Deployment Monoliths
A deployment monolith is an integrated solution comprising different domains:
This approach supports a consistent UI and leads to optimised bundles by compiling everything
together.
A team responsible for a specific sub-domain must coordinate with other sub-domain teams.
They have to agree on overall architecture, the leading framework, and an updated policy for
dependencies. Interestingly, you may consider this an advantage.
It is tempting to reuse parts of other domains which may lead to higher coupling and – eventually –
to breaking changes. To prevent this, you can use free tools like Nrwl’s Nx⁴¹. For instance, Nx allows
you to define access restrictions between the parts of your monorepo to enforce your envisioned
architecture and loose coupling.
⁴¹https://nx.dev/angular
From Domains to Microfrontends 28
.
Having several tiny systems decreases complexity.
If you seek even more isolation between your sub-domains and the teams responsible for them, you
could put each sub-domain into its individual (mono) repository:
You now have something called microfrontends. Microfrontends allow individual teams to be as
autarkic as possible. Each team can choose their architectural style, their technology stack, and they
can even decide when to update to newer framework versions. They can use “the best technology”
for the requirements given within the current sub-domain.
From Domains to Microfrontends 29
The option to use their framework and architecture is useful when developing applications over the
long term. If, for instance, a new framework appears in five years, we can use it to implement the
next domain.
However, this has costs. Now you have to deal with shipping your shared libraries via npm, and this
includes versioning which can lead to version conflicts.
This approach fits product suites like Google or Office 365 well:
From Domains to Microfrontends 30
Each domain is a self-contained application here. This structure works well because we don’t need
many interactions between the domains. If we needed to share data, we could use the backend. Using
this strategy, Word 365 can use an Excel 365 sheet for a series letter.
This approach has several advantages:
• It is simple
• It uses SPA frameworks as intended
• We get optimised bundles per domain
In the screenshot, the shell loads the microfrontend with the red border into its working area.
Technically, it simply loads the microfrontend bundles on demand. The shell then creates an element
for the microfrontend’s root element:
Instead of bootstrapping several SPAs, we could also use iframes. While we all know the enormous
disadvantages of iframes and have strategies to deal with most of them, they do provide two useful
features:
1) Isolation: A microfrontend in one iframe cannot influence or hack another microfrontend in
another iframe. Hence, they are handy for plugin systems or when integrating applications from
other vendors. 2) They also allow the integration of legacy systems.
You can find a library that compensates most of the disadvantages of iframes for intranet applications
here⁴².
⁴²https://www.npmjs.com/package/@microfrontend/common
From Domains to Microfrontends 32
• If we don’t use specific tricks (outlined in the next chapter), each microfrontend comes with
its own copy of Angular and the other frameworks, increasing the bundle sizes.
• We have to implement infrastructure code to load microfrontends and switch between them.
• We have to work to get a standard look and feel (we need a universal design system).
Finding a Solution
Choosing between a deployment monolith and different approaches for microfrontends is tricky
because each option has advantages and disadvantages.
I’ve created the following decision tree, which also sums up the ideas outlined in this chapter:
As the implementation of a deployment monolith and the hyperlink approach is obvious, the next
chapter discusses how to implement a shell.
Conclusion
There are several ways to implement microfrontends. All have advantages and disadvantages. Using
a consistent and optimised deployment monolith can be the right choice.
It’s about knowing your architectural goals and about evaluating the consequences of architectural
candidates.
A lightweight Approach towards
Microfrontends
For implementing Microfrontends with Angular, there are several approaches and here I present one
of them: Loading separately compiled UMD bundles. The source code⁴³ can be found here⁴⁴.
The approach presented here has been inspired by the work of Victor Savkin⁴⁵ and by a
great conversation I had with Rob Wormald⁴⁶ several months ago.
The shell app in our example is capable of loading separately compiled and deployed libraries
like the flight-lib:
⁴³https://github.com/manfredsteyer/module-federation-light
⁴⁴https://github.com/manfredsteyer/module-federation-light
⁴⁵https://twitter.com/victorsavkin
⁴⁶https://twitter.com/robwormald
⁴⁷https://nx.dev/angular
A lightweight Approach towards Microfrontends 34
If we wanted to start the flight-lib in standalone mode, you would use the flight-app which
directly references it.
1 @NgModule({
2 imports: [
3 CommonModule,
4 RouterModule.forChild([
5 { path: 'flights-search', component: FlightComponent },
6 [...]
7 ])
8 ],
9 declarations: [FlightComponent]
10 })
11 export class FlightLibModule {}
There is really nothing special about it. After building it (ng build flight-lib), we get the following
folders:
1 dist/libs/flight-lib
2 ├───bundles
3 ├───esm2015
4 ├───esm5
5 ├───fesm2015
6 ├───fesm5
7 └───lib
Each of these folders contains the library compiled into < specific module format. The UMD bundles
can be found in the bundles folder:
1 18.767 flights-flight-lib.umd.js
2 30.145 flights-flight-lib.umd.js.map
3 5.923 flights-flight-lib.umd.min.js
4 17.766 flights-flight-lib.umd.min.js.map
By looking at the file size of the minimized version, you can tell that it only contains our code and
not the code of the dependencies referenced. Now, you only need to deploy these files – at least
the minimized bundle – to any webserver of your choice. To simplify this demonstration, I’ve just
copied it over to the shell’s assets folder.
A lightweight Approach towards Microfrontends 36
1 @NgModule({
2 declarations: [AppComponent, HomeComponent],
3 imports: [
4 BrowserModule,
5 RouterModule.forRoot([
6 {
7 path: '',
8 component: HomeComponent
9 },
10 {
11 path: 'flights',
12 loadChildren: () => loadModule('assets/flights-flight-lib.umd.min.js')
13 .then(g => g.flights['flight-lib'].FlightLibModule)
14 }
15 ])
16 ],
17 providers: [],
18 bootstrap: [AppComponent]
19 })
20 export class AppModule {}
At first sight, this really looks like traditional lazy loading. However, instead of a dynamic import
I’m using the helper function loadModule. This is needed because when using webpack – and hence
the Angular CLI – a dynamic import assumes that the referenced code is already available at compile
time. Of course, here, this is not the case as we want to load separately compiled stuff.
After loading, the then handler is grabbing the FlightLibModule out of the global namespace.
Fortunately, it is not directly put there but into the sub namespace flight-lib which is part of
the sub namespace flights. Technically, we are talking about nested JavaScript objects here. As
flight-lib contains a dash (-), we have to use the bracket syntax for referencing it.
Here, flights is the name of the Angular project (and hence the Nx workspace) and
flight-lib is the library’s name. If you are not sure about these names you can look
them up in the first 3 to 5 lines of your UMD bundle.
A lightweight Approach towards Microfrontends 37
For such bundles, we also need to expose all libraries it needs via the global namespace. For this, I’m
calling my helper function
1 initExternals(environment.production);
This is a very common way of dynamic script loading which is normally hidden behind libraries. The
moduleMap makes sure each bundle is only loaded once. Please also note that the returned promise
is resolved with the window object which represents the global namespace in the browser.
The helper function initExternals is just putting the shared libraries into the global namespace
where the separately compiled UMD bundles expects them:
A lightweight Approach towards Microfrontends 38
To find out about the right location like ng.core or ng.common, just have a look into this libraries’
UMD bundles. It should be used there in the first 3 to 5 lines of code.
Conclusion
The approach outlined here is simple and can be used with Angular libraries already today. The
bundles are small because they only contain the lazily loaded code and no dependencies. They are
always shared with the shell.
One downside is, that the shell needs to provide all the dependencies the library needs. They
have to be either part of the shell upfront and exposed as shown here or loaded alongside the library
in question. Also, traditional lazy loading does not work from within an UMD bundle as the CLI’s
build task is not splitting UMD such bundles. However, traditional lazy loading works well in the
shell and an UMD bundle can load further UMD bundles as shown above.
The upcoming Webpack 5 Module Federation provides (see chapter about Module Federation)
a lot more convenience out of the box: It doesn’t demand you to create separate libraries and it
allows the microfrontend to directly come with its own dependencies. To be more precise, both, the
microfrontend and the shell can decide which dependencies they want to share. If the shell does
not provide a given dependency, the microfrontend can fall back to loading it directly. Also, Module
Federation defines all the details in a declarative way.
One benefit of the approach outlined here is that we don’t need to know the amount of
microfrontends during compile time. Instead we could load a configuration at runtime telling us
how many microfronts are available and where they can be found. However, eventually there will
be a webpack plugin which allows the same.
A lightweight Approach towards Microfrontends 39
If you want to get started today, the “light” approach shown here might be the best you can have
today. However, keep an eye to the further development of Module Federation and to approaches
for integrating them into the Angular CLI.
The Microfrontend Revolution: Using
Module Federation with Angular
Until now, when implementing microfrontends, you had to dig a little into the bag of tricks. One
reason is surely that current build tools and frameworks do not know this concept. Webpack 5, which
is currently in BETA, will initiate a change of course here.
It allows an approach implemented by the webpack contributor Zack Jackson. It’s called Module
Federation and allows referencing program parts not yet known at compile time. These can be self-
compiled microfrontends. In addition, the individual program parts can share libraries with each
other, so that the individual bundles do not contain any duplicates.
In this chapter, I will show how to use Module Federation using a simple example.
Example
The example used here consists of a shell, which is able to load individual, separately provided
microfrontends if required:
The Microfrontend Revolution: Using Module Federation with Angular 41
Shell
The loaded microfrontend is shown within the red dashed border. Also, the microfrontend can be
used without the shell:
The Microfrontend Revolution: Using Module Federation with Angular 42
The source code⁴⁹ of the used example can be found in my GitHub account⁵⁰.
However, the path mfe1/Module which is imported here, does not exist within the shell. It’s just a
virtual path pointing to another project.
To ease the TypeScript compiler, we need a typing for it:
1 // decl.d.ts
2 declare module 'mfe1/Module';
Also, we need to tell webpack that all paths starting with mfe1 are pointing to an other projects. This
can be done with the ContainerReferencePlugin:
1 new ContainerReferencePlugin({
2 remoteType: 'var',
3 remotes: {
4 mfe1: "mfe1"
5 },
6 overrides: [
7 "@angular/core",
8 "@angular/common",
9 "@angular/router"
10 ]
11 }),
The remotes section maps the internal name mfe1 to the same one defined within the separately
compile microfrontend. The concrete path of this project isn’t defined here. That happens later
when the shell loads a so called remote entry point provided by the microfrontend.
Also, overrides contains the names of libraries our shell shares with the microfrontend.
While the ContainerReferencePlugin shown here is intended for the shell, the ContainerPlugin
used below is the one for configuring the microfrontends.
Perhaps you’ve noticed that the previous chapter only used the ModuleFederationPlugin. However,
this plugin is just delegating to both⁵¹, the ContainerReferencePlugin and the ContainerPlugin.
Projects configured with it can be used as a shell and a microfrontend at the same time.
⁵¹https://github.com/webpack/webpack/blob/dev-1/lib/container/ModuleFederationPlugin.js
The Microfrontend Revolution: Using Module Federation with Angular 44
1 @NgModule({
2 imports: [
3 CommonModule,
4 RouterModule.forChild(FLIGHTS_ROUTES)
5 ],
6 declarations: [
7 FlightsSearchComponent
8 ]
9 })
10 export class FlightsModule { }
In order to make it possible to load the FlightsModule into the shell, we need to reference the
ContainerPlugin in our webpack configuration:
1 new ContainerPlugin({
2 name: "mfe1",
3 filename: "remoteEntry.js",
4 exposes: {
5 Module: './projects/mfe1/src/app/flights/flights.module.ts'
6 },
7 library: { type: "var", name: "mfe1" },
8 overridables: [
9 "@angular/core",
10 "@angular/common",
11 "@angular/router"
12 ]
13 }),
The Microfrontend Revolution: Using Module Federation with Angular 45
The configuration shown here exposes the FlightModule under the public name Module. The section
overridables points to the libraries shared with the shell.
Also, the microfrontend’s configuration must point to the location it will be deployed at:
1 output: {
2 publicPath: "http://localhost:3000/",
3 [...]
4 },
Obviously, it would be better if we could specify this path at runtime. Fortunately, when this chapter
was written, there was already a Pull Request⁵² for this.
1 import('./bootstrap');
This dynamic import gives the infrastructure the time necessary for loading everything the shell
hasn’t already loaded. Hence, in the remaining parts of the application – e. g. in main.ts – we can
leverage the usual static imports:
⁵²https://github.com/webpack/webpack/pull/10703
The Microfrontend Revolution: Using Module Federation with Angular 46
This trick is not needed for the shell shown above because it’s configured with the ContainerReferencePlugin.
If we configured it with the ModuleFederationPlugin instead, we would need to go with the
workaround shown, however.
1 <script src="http://localhost:3000/remoteEntry.js"></script>
This is just a tiny script bridging the gap between the shell and the microfrontend. As the result, the
shell can now load the separately compiled FlightsModule using the path mfe1/Module:
You can find the source code⁵³ for this in my GitHub account here⁵⁴.
that: It enables restrictions defining which libraries can access each other. Nx can detect which parts
of your monorepo are affected by a change and only recompile and retest those.
To make routing across microfrontends easier, it’s a good idea to prefix all the routes with the
application’s name. In the following case, the application name is client-a
1 @NgModule({
2 imports: [
3 ReactiveFormsModule,
4 BrowserModule,
5 RouterModule.forRoot([
6 { path: 'client-a/page1', component: Page1Component },
7 { path: 'client-a/page2', component: Page2Component },
8 { path: '**', component: EmptyComponent}
9 ], { useHash: true })
10 ],
11 [...]
12 })
13 export class AppModule {
14 [...]
15 }
1. Put everything into one bundle, so that this global array is not needed
⁵⁷https://www.softwarearchitekt.at/aktuelles/angular-elements-part-i/
⁵⁸https://www.softwarearchitekt.at/aktuelles/angular-elements-part-ii/
Six Steps to your Angular-based Microfrontend Shell (Heavy-weight approach) 51
I use solution 1) because a microfrontend is, by definition, small. Just having one bundle makes
loading it on demand easier. As we will see later, we can share libraries like RxJS or Angular itself
between them.
To tweak the CLI to produce one bundle, I use my free tool ngx-build-plus⁵⁹ which provides a
--single-bundle switch:
1 ng add ngx-build-plus
2 ng build --prod --single-bundle
1 frontend['visible'] = false;
⁵⁹https://www.npmjs.com/package/ngx-build-plus
Six Steps to your Angular-based Microfrontend Shell (Heavy-weight approach) 52
1. It is irrelevant which order the microfrontends are loaded. When loaded they can grab the
current parameters from the URL
2. It allows deep linking
3. It’s how the web is supposed to work
4. It’s easy to implement
Setting a URL parameter with the Angular router is a simple matter of calling one method:
1 this.router.navigate(['.'], {
2 queryParamsHandling: 'merge', queryParams: { id: 17 }});
The merge option saves the existing URL parameters. If there is already an id parameter, the router
overwrites it.
The Angular router can help listen for changes within URL parameters:
1 route.queryParams.subscribe(params => {
2 console.debug('params', params);
3 });
1. If you wrap your microfrontends into web components, you can use their properties and events
to communicate with the shell.
2. The shell can put a “message bus” into the global namespace: typescript (window as
any).messageBus = new BehaviorSubject<MicroFrontendEvent>(null);
The shell and the microfrontends can subscribe to this message bus and listen for specific
events. Both can emit events.
3. Using custom events provided by the browser: “‘typescript // Sender const customer = { id: 17,
… }; window.raiseEvent(new CustomEvent(‘CustomerSelected’, {details: customer}))
// Receiver window.addEventListener(‘CustomerSelected’, (e) ⇒ { … }) “‘
Six Steps to your Angular-based Microfrontend Shell (Heavy-weight approach) 53
To use webpack externals together with the Angular CLI, you can leverage ngx-build-plus⁶⁰ which
even comes with a schematic introducing the needed changes into your application.
As mentioned above, you can install it with ng add:
1 ng add ngx-build-plus
1 ng g ngx-build-plus:externals
Please remember that within a monorepo you have to provide the name of the project in question:
This approach also introduces an npm script build:<project-name>:externals. For the default
project, there is a script build:externals too.
If you look into the index.html after running this script, you see that Angular is directly loaded:
⁶⁰https://www.npmjs.com/package/ngx-build-plus
Six Steps to your Angular-based Microfrontend Shell (Heavy-weight approach) 55
To optimise this, one could put those parts of Angular into one bundle.
If you look into the generated webpack.externals.js, you find a section mapping package names
to global variables:
This method, for instance, makes the produced bundle reference the global variable ng.core when
it needs @angular/core. Hence, @angular/core no longer needs to be in the bundle.
Please note that this is not the default operation mode for Angular and has some risks.
Conclusion
With the right wrinkles, implementing a shell for microelements is not difficult. However, this is
only one approach to implementing microfrontends and has advantages and disadvantages. Before
implementing it, make sure it fits your architectural goals.
Literature
• Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software⁶¹
• Wlaschin, Domain Modeling Made Functional⁶²
• Ghosh, Functional and Reactive Domain Modeling⁶³
• Nrwl, Monorepo-style Angular development⁶⁴
• Jackson, Micro Frontends⁶⁵
• Burleson, Push-based Architectures using RxJS + Facades⁶⁶
• Burleson, NgRx + Facades: Better State Management⁶⁷
• Steyer, Web Components with Angular Elements (article series, 5 parts)⁶⁸
⁶¹https://www.amazon.com/dp/0321125215
⁶²https://pragprog.com/book/swdddf/domain-modeling-made-functional
⁶³https://www.amazon.com/dp/1617292249
⁶⁴https://go.nrwl.io/angular-enterprise-monorepo-patterns-new-book
⁶⁵https://martinfowler.com/articles/micro-frontends.html
⁶⁶https://medium.com/@thomasburlesonIA/push-based-architectures-with-rxjs-81b327d7c32d
⁶⁷https://medium.com/@thomasburlesonIA/ngrx-facades-better-state-management-82a04b9a1e39
⁶⁸https://www.softwarearchitekt.at/aktuelles/angular-elements-part-i/
About the Author
Manfred Steyer
Manfred Steyer is a trainer, consultant, and programming architect with focus on Angular.
For his community work, Google recognizes him as a Google Developer Expert (GDE). Also, Manfred
is a Trusted Collaborator in the Angular team. In this role he implemented differential loading for
the Angular CLI.
Manfred wrote several books, e. g. for O’Reilly, as well as several articles, e. g. for the German Java
Magazine, windows.developer, and Heise.
He regularly speaks at conferences and blogs about Angular.
Before, he was in charge of a project team in the area of web-based business applications for
many years. Also, he taught several topics regarding software engineering at a university of applied
sciences.
Manfred has earned a Diploma in IT- and IT-Marketing as well as a Master’s degree in Computer
Science by conducting part-time and distance studies parallel to full-time employments.
You can follow him on Twitter (https://twitter.com/ManfredSteyer) and Facebook (https://www.facebook.com/manfr
and find his blog here (http://www.softwarearchitekt.at).
Trainings and Consultancy
If you and your team need help regarding Angular, we are happy to help with our online and on-site
workshops.
• Angular Workshop⁶⁹
• Angular Architecture Workshop (Advanced)⁷⁰
• Architecture Consultancy
• Reviews
• Project Support
• Q&A Workshops
⁶⁹https://www.angulararchitects.io/schulungen/angular-strukturierte-einfuehrung/
⁷⁰https://www.angulararchitects.io/schulungen/advanced-angular-enterprise-anwendungen-und-architektur/
⁷¹https://www.angulararchitects.io/beratung/
⁷²https://www.angulararchitects.io/subscribe/
⁷³https://twitter.com/ManfredSteyer