Sustainable Angular Architectures with Strategic Design and Monorepos - Part 2: Implementation

In the first part of this series, I've presented the idea of Strategic Design which allows to subdivide a software system into several self-contained (sub-)domains. In this part, I show how to implement those domains with Angular and an Nx-based monorepo.

For this, I'm following some recommendations the Nx team recently wrote down in their free e-book about Monorepo Patterns. Before this was available, I've used similar strategies but in order to help to establish a common vocabulary and common conventions in the community, I seek to use this official way.

The sample I'm using to demonstrate this, can be downloaded here.

Implementation with Nx

For the implementation of the defined architecture, a workspace based on Nx is used. This is an extension for the Angular CLI, which among other things helps to break down a solution into different applications and libraries. Of course this is just one of several possible approaches. As an alternative, one could, for example, implement each domain as a completely separate solution. This would be called a micro-app approach.

The solution shown here puts all applications into one apps folder, and all the reusable libraries are grouped by the respective domain name in the libs folder:

Folder structure of the used monorepo

Because such a workspace consists of several applications and libraries that are managed 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 case for the development of .NET solutions in the Microsoft world for about 20 years.

It allows the sharing of source code between the project participants in a particularly simple way and also prevents version conflicts by having only one central node_modules folder with dependencies. This ensures that e.g. each library uses the same Angular version.

In order to create the CLI-based monorepo workspace, just leverage the create-nx-workspace command. Then, you can use ng generate to add applications and libraries:

npx create-nx-workspace myworkspace --ignore-existing cd e-proc ng generate app ui ng generate lib feature-request-product

The npx command downloads everything that's necessary to create the workspace.

When calling ng generate app or ng generate lib within an Nx monorepo, the CLI asks for a directory name. Here, we can specify the name of the current domain, so that the folder structure reflects the application's domains.

Categories for Libraries

In their free e-book about Monorepo Patterns, Nrwl -- the company behind Nx -- use the following categories for libraries:

  • feature: Implements a use case using smart components
  • data-access: Implements data accesses, e.g. via HTTP or WebSockets
  • ui: Provides use case agnostic and thus reusable components (dumb components)
  • util: Provides helper functions

In addition, I'm also using the following categories:

  • shell: For an application that contains multiple domains, a shell provides the entry point for a domain
  • api: Provides functionalities exposed for other domains
  • domain: Domain logic like calculating additional expanses (not shown here). Can be consolidated with a corresponding data-access library to simplify the design.

To keep the overview, the categories are used as a prefix for the individual library folders. Thus, libraries of the same category are presented next to each other in a sorted overview.

Each library also has a public API through which it publishes individual components. On the other hand, they hide all other components. These can be changed as desired:

export * from './lib/catalog-data-access.module'; export * from './lib/catalog-repository.service';

Check accesses to libraries

To improve maintainability, it is important to minimize the dependencies between the individual libraries. The achievement of this goal can be checked graphically with Nx. For this, it provides the dep-graph npm script:

npm run dep-graph

If we just concentrate on the Catalog domain in our case study, the result looks as follows:

Catalog Domain with public API

In the case considered here, a few rules are used for the communication between libraries and these lead to a consistent layering. For example, each library may only access libraries from the same domain or shared libraries.

Access to APIs such as catalog-api must be explicitly granted to individual domains. The categorization of libraries also has limitations: a shell only accesses features and a feature accesses data-access libraries. In addition, anyone can access utils.

To enforce such restrictions, Nx comes with its own linting rules. As usual, they are configured within tslint.json:

"nx-enforce-module-boundaries": [
"allow": [],
"depConstraints": [
{ "sourceTag": "scope:catalog", "onlyDependOnLibsWithTags": ["scope:catalog", "scope:shared"] },
{ "sourceTag": "scope:shared", "onlyDependOnLibsWithTags": ["scope:shared"] },
{ "sourceTag": "scope:ordering", "onlyDependOnLibsWithTags": ["scope:ordering", "scope:shared", "scope:catalog/api"] },

{ "sourceTag": "type:app", "onlyDependOnLibsWithTags": ["type:shell"] },
{ "sourceTag": "type:shell", "onlyDependOnLibsWithTags": ["type:feature", "type:util"] },
{ "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:data-access", "type:util", "type:api"] },
{ "sourceTag": "type:api", "onlyDependOnLibsWithTags": ["type:data-access", "type:util"] },
{ "sourceTag": "type:util", "onlyDependOnLibsWithTags": ["type:util"] }

According to a suggestion from the mentioned e-book about Monorepo Patterns, the domains are named with the prefix scope and the library types are prefixed with type. Prefixes of this type are only intended to increase readability and can be freely assigned.

In addition, this example also shows the domain Ordering which, according to the context mapping, has access to the CatalogApi. For this, the example uses a name prefix to make sure that only selected libraries are allowed to access the api.

The mapping between the projects and the library types and domains shown here takes place in the file nx.json:

"projects": {
"ui": {
"tags": ["scope:app"]
"ui-e2e": {
"tags": ["scope:e2e"]
"catalog-shell": {
"tags": ["scope:catalog", "type:shell"]
"catalog-feature-request-product": {
"tags": ["scope:catalog", "type:feature"]
"catalog-feature-browse-products": {
"tags": ["scope:catalog", "type:feature"]
"catalog-api": {
"tags": ["scope:catalog", "type:api", "scope:catalog/api"]
"catalog-data-access": {
"tags": ["scope:catalog", "type:data-access"]
"shared-util-auth": {
"tags": ["scope:shared", "type:util"]
"ordering-feature-send-order": {
"tags": ["scope:ordering", "type:feature"]

Alternatively, these tags can also be specified when setting up the applications and libraries.

To test against these rules, just call ng lint on the command line. Development environments such as WebStorm / IntelliJ or Visual Studio Code show such violations while typing. In the latter case, a corresponding plugin must be installed.

Don't expect that those rules always guarantee a beautiful dependency graph without overlappings as shown above. However, if you put each domain into a block diagram where each layer is only allowed to access layers beneath it, you can clearly see that you have got a clean and comprehensible architecture:

The result is a clean layered architecture

Moreover, everyone clearly sees where specific parts of the application are supposed to be found and the introduced rules prevent cycles, at least if APIs can only be accessed by features of other domains.

Finegrained Libraries

Perhaps you are wondering, why we are using such fine-grained libraries here. Well, there are several reasons:

  • In Nx, the library can be the unit of recompilation. It comes with scripts which are just recompiling libraries affected by changes. For this, it looks into your GIT history.
  • The libary can also be the unit of retesting.
  • Defining access restrictions is easier when the libraries are fine grained
  • Libraries might be a EcmaScript-conform replacement for NgModules once they become optional.

Isolate your domain

In order to isolate your domain, it's a good idea to place the data access code into a seperate layer. DDD is also using a facade placed in front of the domain. This facade is called application service. It is responsible for providing domain logic in an use case specific way. For those layers you could create further libraries or you could just subdivide one library. The former one is for sure the cleaner solution, the latter one is a bit easier to accomplish:

The result is a clean layered architecture

Conclusion and Evaluation

Strategic Design provides a proven way to break an application into self-contained domains. These domains are characterized by their own specialized vocabulary, which must be used rigorously by all stakeholders.

The CLI extension Nx provides a very charming 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.

Of course, it can be argued that an Angular client does not necessarily include domain logic. But the fact is that more and more logic can also be found on the client, especially with single page applications. Regardless, the outlined ideas of Strategic Design have proven to be extremely useful for getting a good cut.


Hier können Sie eine Anfrage für eine unverbindliche Schulung ode Beratung bzw. einen Workshop erstellen.
Unverbindliche Anfrage


Angular Schulung: Strukturierte Einführung

Lernen Sie in dieser interaktiven Schulung anhand einer Beispielanwendung den Einsatz von Angular für Ihre erfolgreichen Projekte kennen. Sie durchdringen die Hintergründe und bauen von der ersten Minute an auf Best Practices auf.


Advanced Angular: Enterprise-Anwendungen und Architektur

In dieser Schulung erfahren Sie alles für die Entwicklung großer Anwendungen mit Angular: Mono-Repos, Micro-Apps, State Management, Performance und mehr


Angular: Strukturierte Einführung

Seit der Ankündigung von Angular (2+) fragen sich Entwicklungs-Teams, welche Migrationspfade für AngularJS-1.x-Anwendungen vorliegen werden. Das im Lieferumfang von Angular enthaltene ngUpgrade bietet eine Antwort darauf. Es erlaubt einen Parallelbetrieb von AngularJS 1.x und Angular (2+) und stellt somit die Grundlage für eine schrittweise Migration dar.


Progressive Web-Apps mit Angular

Progressive Web Apps bieten den Komfort nativer Anwendungen, indem sie auf moderne Browser APIs, wie Service Worker, setzen. Sie sind installierbar sowie offlinefähig und nutzen Hintergrundprozesse für Datensynchronisation und Push-Notifications. Diese Schulung zeigt anhand eines durchgehenden Beispiels was sich genau hinter diesem neuen Konzept verbirgt, wie solche Anwendungen mit Angular entwickelt werden und wie Sie in Ihren Projekten von den dahinterstehenden Ideen profitieren.


Reaktive Architekturen mit Angular und Redux

Dieses interaktive Seminar vermittelt, wie Sie reaktive Anwendungen mit Angular entwickeln können.



TypeScript gibt Ihnen alle Möglichkeiten der neuen JavaScript-Standards und zusätzlich ein statisches Typsystem, das dabei hilft, Fehler möglichst früh zu erkennen. Außerdem ist TypeScript die Grundlage für Angular. In diesem interaktiven Seminar lernen Sie diese mächtige Sprache anhand einer Fallstudie kennen.


Weitere Schulungen ...