by Peter B Smith
Every weekday for five years I wrote, reviewed or researched Angular codebases. Every weekday for five years I learned something new, useful or peculiar. I did this as a Partner at Upstate Interactive, a speaker at AngularNYC and briefly as a show host on the ng-conf channel.
My partner in all this was Zoe Augustinos, also from Upstate Interactive, who shares culpability in choosing to use NativeScript Schematics over xplat; both were purported to be useful for cross-platform development when only the latter was. We learned just as much about what not to do as we did about what to recommend.
- Structuring Modules and Avoiding Huge Common Bundles
- Fat Libs, Thin Apps and Shoulders Broad Enough to Stand On
- Fat Libs
- Thin Apps
- Shoulders Broad Enough to Stand On
- The Results of Organization
- Structuring Modules Cheatsheet
- Monorepos Beyond Nx
- Inflection Points: Evaluate the Size of Your Team and Codebase
- Roll-Your-Own Angular Monorepo
- Tools Outside of Angular
- Real-World Example
- Monorepos Cheatsheet
- Using Domain-Driven Design With Enterprise Teams
- Briefly, Domain-Driven Design in Angular
- DDD Layer Terminology
- Layering a Team With DDD
- Team Role Terminology
- Real-World Example
- Using Domain-Driven Design With Enterprise Teams Cheatsheet
- Watch and Read About DDD
- 10 Tips for Enterprise Angular Development
- Enterprise Angular Development Resource List
In the book that follows, I deliver all that I can to you in four discrete sections. First, structuring modules and avoiding huge common bundles is about thin apps and fat libs. The second, monorepos beyond Nx is a discussion about an alternative to using the popular Nx tool to maintain a monorepo. The third, using Domain-Driven Design with your teams is a template for building Angular teams that extends from my work with Manfred Steyer. Fourth and finally are 10 tips for enterprise Angular development.
This book is none of what not to do. To learn what not to do, you must discover for yourself the depths of Angular. I maintain that God lies in the details. So you will have fun with that discovery. This book is only my recommendations from Angular codebases I researched, reviewed or wrote for five years every weekday.
Structuring Modules and Avoiding Huge Common Bundles
When you begin in Angular, you start with an app. Within the context of a project that starts from Angular CLI, that app lives in a
src directory. Within that
src directory, it's easy to get all of your code organized within a self-evident structure of your own making. That might mean putting your services all together in a
services directory and your dumb components in a
components directory and your smart components in a
containers directory. You might go further and subdivide your app into modules that can take advantage of router-based lazy-loading. As soon as you begin to build applications at Angular Enterprise scale, all that self-evident structure will fail.
What to do when the Angular CLI structure fails to serve your needs? Up your directory organization and learn from others in the community. Along the way, the Angular framework has grown along with the community and provided better tools for massive codebases that include multiple applications in a single repository with support for
libraries in the angular.json file.
What does enterprise scale mean? This book considers enterprise to mean an organization of 15 or more engineers working on five or more distinct web applications.
Fat Libs, Thin Apps and Shoulders Broad Enough to Stand On
"Fat libs" is an approach to architecting Angular repositories that embraces a philosophy of including the majority of your code, including code that is meant for applications, in a
/libs directory because that allows a high degree of parallel development.
"Thin Apps" conversely means that your applications will have minimal amounts of code, favoring the import of libraries over direct inclusion of code.
Shoulders Broad Enough to Stand On
There are two strong examples in the community that highlight the philosophy of fat libs and thin apps. The first, xplat, is built to accommodate an arbitrary number of applications that work across multiple platforms including web, iOS and Android. The second, Domain-Driven Design (DDD), is built to manage many applications by partitioning business logic by domain. Directory structures for both highlight their underlying philosophies.
Example Directory Structure for xplat
apps/ nativescript-admin/ nativescript-coach/ web-app/ web-app-e2e/ libs/ xplat/ core/src/lib/ base/ environments/ models/ services/ features/src/lib/ review/ ui/ nativescript/ core/ features/src/lib/ review/ ui/ scss/ utils scss/ utils/ web/ core/ features/
- Code is separated by platform, like "nativescript" and "web." Electron and Ionic are other possible options.
- Duplication of named directories like
scssis a result of segmenting code by platform as well as including generalized, shareable code for cross-platform development.
- Features directory includes the UI components necessary for their functioning as children.
Example Directory Structure for DDD
apps/ admin/ app/ coach/ libs/ shared/ domain/ ui-components/ util-core/ util-environments/ util-models/ util-styles/ training/ domain/ feature-administrating/ feature-auth/ feature-coaching/ feature-public/ feature-review/ publishing/ domain/ feature-social/ feature-email/
- Code is separated by domain. In this example and most real applications of DDD there is the "shared" domain—a special domain that contains code meant to be used across the project.
- Directories are prefixed according to the function of the code. "feature" and "util" and "ui" and "domain" are all related in a logical stack that creates boundaries on how they can be imported.
- This project includes "styles" and "environments" utility libraries. This is optionally decided by the developer.
Official Nx plugin
Embraces fat libs, thin apps
Supported by widely published theory
The Results of Organization
There is a good reason for a developer to leverage a "fat libs, thin apps" approach to developing Angular applications. It makes their lives easier in the long and short run. It creates a well-placed foundation for them to build their applications on top of.
Modules Optional? Angular's core team is working on a future where modules are entirely optional
Beyond their own benefit, developers provide benefits to their users. The structured approach enables the Angular build pipeline to choose only the code necessary and bundle it together to create an effectively smaller and faster final product.
Structuring Modules Cheatsheet
AppModules are for coordination of other modules. Are you declaring components here? You can find a better place for them, probably in
- Within a monorepo
AppModules duplicate code. If you're using NgRx, then all of them will have
StoreModule.forRoot. Could you DRY it out? Yes, with no tangible performance benefit for the end user and likely added confusion for your team.
- Leverage prior art to structure your libs. I like DDD and xplat—both can get you to the same place—and there's more options than those two. Not ready to adapt a structure made by someone else? Look around, you're in Angular. Embrace the fact that the entire community has spent years learning to develop Enterprise applications for the modern era and you'll go farther, faster.
- Emulate the @angular/material structure for your design system. If you're going to be building reusable components that all your apps share, check the source code for Angular Material and look at the surface area of the API, and you'll see that the whole system exposes modules as the primary export. You can use the same structure for your own component libraries to keep your imports small.
- Modules are prone to cyclical dependencies. Even the most careful developers I know find themselves occasionally creating cycles. You can get by with a cyclical dependency, yet keep an eye out and as soon as you notice one, review your structure and correct it. "A stitch in time saves nine" and an acyclic module tree in mind saves time.
Monorepos Beyond Nx
Nx is the obvious topic of discussion of Monorepos in an Angular conversation because it is the best public example. With Nrwl's pedigree, it has become the de facto monorepo tech in the Angular ecosystem. Increasingly, it is extending to React and beyond.
That said, what goes on behind the enterprise firewall reveals a more interesting world of building Angular applications in a monorepo. It is worth exploring what a structure looks like that has both AngularJS and Angular projects combined, what it looks like to include any arbitrary backend, what it looks like to include arbitrary containers. As in, it is worth it to compare Nx to a roll-your-own approach to monorepos.
At the time of writing, the Nx community plugin ecosystem has a limited set of plugins—only 46. That can be a lot if you are attempting to build something from scratch. On the other hand, if you already have a codebase and there is not 100% plugin coverage of your tech stack, then using Nx can mean customization. Customization might be the jumping off point for exploring a roll-your-own approach.
You may be able to easily if you're on the smaller side of enterprise. With that idea, that there's a space to be successful before you use Nx. Then you can grow to use Nx once necessary. You can always include Nx later.
Inflection Points: Evaluate the Size of Your Team and Codebase
It seems like the term "enterprise" is used as a catch-all either to justify higher billability for SaaS companies or justify higher billability for software agencies. Few companies though describe themselves as "enterprise sized." More practically, a company benefits from a nuanced look at the size and complexity of its codebase and team to decide what monorepo approach is best for them.
Many Teams, Many Products, Few Commonalities
Complexity demands coordination. Nx is good in scenarios where the company implementing it has many teams totaling 15 or more developers and five or more products with few commonalities.
Tightly Coupled Applications
Nx might not be worth it in cases where there are tightly coupled products. Either client and server or multiple similar clients. A heuristic is: fewer than 10 people with five or fewer products that are related and not completely different, then a roll-you-own approach will work.
Roll-Your-Own Angular Monorepo
When exploring Angular for enterprise outside of Nx, I still recommend taking an approach of "fat libs, thin apps," and this is possible because support for multiple apps and multiple libraries is built into the Angular CLI package. On the other hand, the toolkits like xplat and DDD are implemented specifically as Nx plugins and that leaves you to manage your own library architecture.
Frontend and Backend
One way to build your tightly coupled backend and frontend in the same repo is to make a
backend directory sibling to
frontend directory, and from there install your Angular apps inside frontend with a
package.json solely for frontend. Then in the
backend do the same, with a separate package.json. Finally, a root-level
package.json for npm scripts exclusively. This is good in cases where a single package.json for both might lead to cumbersome installs. In one developer's experience, a major scraper project that was almost exclusively backend managed to slow down installs when using a shared package.json.
package.json // npm scripts only frontend/ angular.json package.json backend/ package.json shared/
You may be better for minimizing code sharing. As in, as a sibling to
Angular and AngularJS
frontend directory, use two directories like
Tools Outside of Angular
What fun is it to architect a project with the constraint that you can only use pre-existing architectures? With a roll-your-own monorepo approach, multiple teams can coordinate and consume everything from services to applications, all independently developed, without being constrained by the plugin availability of Nx.
Here "containers" refers to any third-party project which your product relies on, yet is only configured and not actively developed. For example, you might want to include Elasticsearch in your monorepo in a
For any configuration that is necessary across your product, a simple
config directory that is gitignored gives you a place to keep secrets.
.circleci/ .github/ .vscode/ config/ containers/ elasticsearch/ nginx/ infrastructure/ localhost/ ng1/ ng2/ backend/ frontend/ shared/ .editorconfig README.md dockerignore package.json prettier.config.js
In this example the developers chose to make the frontend and backend that service the Angular product siblings nested in an
ng2 directory. As well, the developers have an
infrastructure directory to house code related to terraform.
- Nx can live inside another repo. Everything's custom at the enterprises I work with—no different for the structure of repositories. In cases where there's a desire to combine services like Elasticsearch into your monorepo, then you can move Nx into a non-root directory for fuller scope.
- You can roll your own solution. Nx is powerful, configurable and robust. Nx is tedious and boilerplate-ridden and opinionated. Depending on your perspective, you may find your way toward rolling your own solution.
- Single version policy is always good. There has not been one situation in my experience where it has been helpful to have, for example, an Angular 8 dependency and an Angular 12 dependency. If you are considering having both, consider the costs, which have been well written about elsewhere, and re-evaluate.
Using Domain-Driven Design With Enterprise Teams
Briefly, Domain-Driven Design in Angular
Domain-Driven Design (DDD) has been in the software lexicon for roundabout two decades. It was introduced into the Angular space more recently by Manfred Steyer. It was then implemented into an Nx plugin with the npm package @angular-architects/nx-ddd-plugin. The plugin adds dependency constraints and a set of opinions that turns Angular architectures into highly scalable projects with many teams.
DDD Layer Terminology
DDD within Angular uses directories to implement multiple conceptual layers of code. With utilities as the bottom layer, a utility library cannot depend on any other library within the project. With applications as the top layer, an application can depend on anything. Here are the layers, ordered according to the hierarchy enforced by the DDD plugin.
Talk, All We Do is Talk Shameless plug, I did the NgRx implementation in the DDD plugin and spoke about it with Manfred Steyer. Have a watch here.
- app As described, the app is a thin module that imports its functionality from elsewhere. In DDD this is the top-most abstraction and therefore nothing can depend on the code inside an app.
- feature A feature is a library that coalesces UI components, domain code and utilities into functionality recognizable by users. Apps tend to import these directly. Multiple applications can and often do depend on the same feature. The layer includes smart components, routing and guards.
- UI Component libraries live at the UI layer. It's common for architectures to implement design systems consisting of many reusable presentational components. In DDD this layer is logically isolated and may only depend on domain code and utilities.
- domain The domain layer can be split into three nested layers: application, entities and infrastructure. In practical terms, the domain layer holds a major portion of the application's logic.
- application The domain application layer holds the state-handling logic. This often includes subject-based services and NgRx code. Facades, services that expose a point for domain logic to be consumed and modified from, are implemented here to provide a stable public API to layers above the domain.
- entities Entities are the models for your domain. The entities may be the TypeScript interfaces that the application depends on. This layer is also a solid spot for mock data to live.
- infrastructure Infrastructure code is meant for direct communication with the outside world. That is often everything from HTTP Services to services that manage LocalStorage.
|DDD Layer||Angular Skills|
@angular/core, @angular/router, RxJs
CSS, RxJs, @angular/core, @angular/forms
The implementation of the DDD architecture is trivial on new applications. There is greater overhead in migrating existing applications. The main cause of overhead is the removal of cycles. When analyzing code from a typical Angular application, there are dependency cycles that are generally harmless byproducts of logically distinct code being organized ad hoc. The conversion to DDD, which enforces a linear dependency structure, is an undertaking in reorganization of code more than rewriting.
Layering a Team With DDD
So how to make sense of Angular? Domain-Driven Design helps.
For a senior developer, it can be overwhelming to help a team to grow and build a product. That job is made easier with the clear boundaries Domain-Driven Design provides. For example, a developer focusing on design can focus on the component libraries in the UI layer. For another example, an Angular developer with three years working in the framework is probably being underutilized when she is wiring up HttpServices during times when deep knowledge of RxJs is sought after for a complex UX implementation. DDD makes it easy to isolate skillsets to make the best of each developer's strengths.
Team Role Terminology
Component Developer The role is involved exclusively with the development of components, their layout on a page, and working with provided, often mocked, data.
Component Developer Angular Skills
- Consuming Observables
- Component lifecycle hooks
Feature Developer This role is involved with providing data to components from any source, working with state management, isolating functionality for performance benefits.
Feature Developer Angular Skills
- Working with Subjects, BehaviorSubjects and the entire RxJs package
- Creating facades
- Working with NgRx state packages
Application Developer This role extends from feature developer to include development of the patterns that will be used across the application. That means coordinating reusable components and utilities, creating and enforcing coding standards and application-wide data models.
Application Developer Angular Skills
- Libraries and applications
Monorepo Developer This role is responsible for company-wide coordination of applications and libraries.
Monorepo Developer Angular Skills
- Single-version dependency policies
- Version upgrade management
- Application isolation
- Angular Schematics
Aligning the development of a large scale, enterprise application to a development team is a core benefit of using Angular. There is a constant balancing act that a software organization attempts. On the one side, architecture. On the other, developer independence. Angular provides an architecture that independent developers can work with.
A challenge of working with architecture is the learning curve. It is one of the most talked about topics in the world of Angular. Detractors state that the learning curve and adoption costs are steep. Promoters will concede there is a learning curve and, in return for getting educated, you receive benefits similar to those that Google achieves, mainly stability and scalability.
If you are not Google, then you may not have the resources at your disposal to train your development team on the entire Angular framework.
When there's a will, there's a weigh. Weighing the cost of the learning curve versus the benefits of the enterprise adoption of Angular is your comparison to make. You can lower the cost by incrementally learning Angular and there are sane approaches to do that. DDD is one.
This example shows the state of flexin as it was ages ago. It is not meant to be run. It is meant to be reviewed for its usage of DDD.
Using Domain-Driven Design With Enterprise Teams Cheatsheet
Watch and Read About DDD
- Domain-Driven Design and Angular | Manfred Steyer | ng-conf: Hardwired
- DDD Plugin for Nx: Solid Architectures With the Push of a Button
- @angular-architects/ddd v1.1 overview
- Enterprise Angular, 3rd Edition
- Tutorial – First Steps with Nx and Angular Architecture
- Sustainable Angular Architectures with Strategic Design and Monorepos - Part 1: Methodology
- Sustainable Angular Architectures with Strategic Design and Monorepos - Part 2: Implementation
- Tactical Domain-Driven Design with Angular and Monorepos?
10 Tips for Enterprise Angular Development
Staying up-to-date is not trivial
Major efforts are made by the core Angular team to create easy upgrade paths from version to version. As the application grows, those efforts are more valuable to the transition from any version to the next. There are any number of challenges to the upgrade based on the way the application was developed. For example, building an application with NativeScript and NgRx means that upgrading is not possible on the new version’s release date because the ecosystem of libraries the application relies on are not yet compatible.
It’s more common now than two years ago to build Angular applications with Angular libraries as the primary stores of code instead of applications themselves. The benefits gained from librarying are magnified as the application grows. There are many architecture templates that can make it easier to start cleanly organized library patterns and two strong choices that come to mind are xplat and DDD.
Thoughtfully bring on new developers
Change detection strategies and router guards and ngOnChanges methods and viewchildren and facades and dependency injection and on and on—Angular is a lot to learn. As the application has grown, so has the need to educate developers who are experienced as engineers and nonetheless new to Angular. This is true of any project and in Angular there is often a simple way of reaching a desired outcome by making use of the framework’s built-in tools. Knowing those tools exist can be possible when one has been thoughtfully introduced to the application, given high-quality resources to educate oneself, and given time with experienced Angular developers to review concepts.
The framework was developed well from the start
There are tools and methods available within the framework that have been around since Angular 2 was in the Release Candidate stage that are being used for the first time this month on this application. It can be easy to overlook how complete the framework is when it is the main framework one works in.
There is more high-quality code written in this application as a result of watching YouTube than there is from reading the documentation. The documentation written by the Angular team is by all measures up-to-date, robust and informative. As good as it is, the documentation stops short of diving into the techniques and concepts that will deliver quality enterprise code. YouTube has an ever-updating library of content published by developers who are building some of the world’s most complex and robust applications using the Angular framework. Pursuing that library regularly is as good a way to learn as attending any workshop.
RxJs is there for good reason
Small applications in Angular can rely on imperative programming styles because there is not a large volume of code to manage. At a certain size, RxJs becomes essential. Knowing what a Subject is and how it differs from a BehaviorSubject matters. Being able to quickly determine which operator will get the job done is crucial—not that memorizing all of them is necessary. When the entire application is built reactively rather than in any other way, then the speed of development is maintained as the application grows.
Choose npm packages selectively
True in any framework, for enterprise application development there is a more justifiable cause to build a library rather than rely on an npm package. As soon as an npm package is no longer maintained, it becomes a liability. There may be a need to fork it to keep it functioning in your application, or there may be compatibility issues because of the author’s decisions related to the API. At the outset of investigating a package, a question that is more important in enterprise application development than in other contexts is, “Do we use this npm package or build something ourselves?”
The framework is built on TypeScript. At the outset of application development, there are ways to skip over using TypeScript within the application’s codebase without issue. As in, creating types and interfaces and strong-typing the return value of methods might not be all that necessary. As the application takes on a life of its own and is supported by the contributions of many developers, then TypeScript is invaluable. Using it can make the codebase easier to understand without documentation. It may not replace documentation—more like it could answer questions a developer is asking before documentation exists. As in, when wondering whether the “id” property of an object needs to be a number or a string, TypeScript can provide that answer whether documentation of some other kind exists or not. Beyond the basics, being able to work with generic types, unknown and never types, unions and more provides a developer with tools to annotate the codebase in highly effective ways.
Contribute as a part of your job
Making contributions to open-source packages does not directly lead to increases in revenue or salary. It has a direct benefit to your own ability to develop using any of the packages you rely on. For example, contributing documentation improvements, such as fixing typos, reinforces the knowledge you have and does not require you to learn how the package was built. By fixing documentation, you are scrutinizing the public API of the package and you are reinforcing your knowledge of it. Next time you use one of those methods, you will be sure that you are working with it as intended or you will be sure that you are bending an implementation to meet your needs.
Keep an eye and ear on the community
Angular is as robust a web framework as exists in the world. Still, the community of developers who use it find ways to fill in the gaps that the core team does not have capacity for on their own. Want to make a mobile application? NativeScript. Have a massive application? Nx. Is the application data complex? NgRx. Cannot write code fast enough? Schematics.
The quantity of public code available to make Angular an extensible framework for application development is as voluminous as a galaxy, and the quality of the best packages is as shining as any star in the sky. Keeping your head up and seeing what others are working on is a vital part of the process of enterprise application development.
Enterprise Angular Development Resource List
- Nx monorepo examples
- NgRx real-world examples
- nx-ddd-plugin README
- DDD Plugin for Nx: Solid Architectures With the Push of a Button