Monorepo Pattern: Setting up Angular workspace for multiple applications in one single repository
Introduction:
In this article, I would like to discuss my experience while setting up an Angular project to use the Monorepo pattern. The Monorepo, as the name suggests mono (single) and repo (repository of the codebase) is a single source of truth for the entire organization code base. Monorepo as such is a broad topic, in this article, my focus would be primarily on creating an angular workspace that uses monorepo pattern.
Pre-requisites:
1. Angular Framework: Basic understanding of Framework and its associated architecture.
2. Typescript: Primary language is used by the Angular Framework.
3. NodeJS: As a development server and as a dependency manager via Node package manager (NPM).
4. Angular CLI: Angular Command-line interface to setup angular workspace and generate boilerplate code using commands.
5. Visual Studio Code: Text editor (or any other popular editor WebStorm, Atom, Sublime Text, Brackets, etc.,)
Thank you to the Angular Team/Community and all supporting tools that make developers life easier and easier day by day
Scope of the Article:
As mentioned in the introduction, the scope of this article is limited to setting up an angular workspace for building a web-based UI/UX application. We will try to understand how the files related to multiple web applications can be grouped together into one single repository which allows us to re-use without creating standalone libraries which may be added as a dependency in package.json. I will walk through a list of angular CLI commands I used to set up the workspace with multiple sub-projects and re-usable shared libraries.
Also, this is not a one fit architecture solution for all web-based applications as each one is different and needs to be handled case by case. This would be one of many such use cases which may definitely have some flaws but my main intention is to give some idea. Again, to be clear my intention is not to discuss why monorepo? but how to monorepo?.
Made-up Beehive Story:
Once upon a time, there was a Queen honey-bee named “Lucky” who tasked to prepare colorful Honey bottle(s) mixed with emotions( happy, Angry, Shock, Sad) as flavors. To achieve this, lucky decided to use RED, GREEN, BLUE Beehive(s) colonies (standalone sub-projects) that hold horizontal cells (modules/components) for storing honey with emotions as flavors. Honey bottle(s) (Integration Sub-Projects) are used to mix and match different flavors of honey from the beehive colonies(components/modules standalone sub-projects) with some spices as pollen extracted from flowers (library sub-project).
Beehive(s) Colonies ===> Stadalone sub-projects
Horizontal cells ===>modules/components
Honey Bottles ===> Integration sub-projects
Flowers==>pollens==> library sub-project
I know it’s too much imagination. Well, I tried :-). Also, truth to be told, this story partly inspired by hearing from my 4-year-old son who might have learned it from his lot of TV time he is getting these days. Hopefully, the below picture gives a better idea?
The ng-beehive-monorepo
Angular Workspace can be divided into sub-projects which are categorized into:
- Standalone Sub-project(s): These are standalone projects which hold modules/components falling under one set of business functionality or domain. These can be used to launch the application independently or The modules defined in this project are exposed to be lazy-loaded into the integration sub-integration. Also, code sharing between standalone sub-projects should be avoided in order to avoid circular dependencies. See picture:
beehive-red, beehive-green, beehive-red
- Integration Sub-Project(s): These primarily work as integration project which consolidates bits and pieces from standalone and library projects and serves it as a web-application.
See picture:beehive-rgb, beehive-rg
- Library Sub-Project(s): The library sub-projects hold any components/modules/directive/pipes/interceptors that may be used in more than on sub-project or integration project.
see the picture:lib-beehive-RG-shared, lib-beehive-UI-shared, lib-beehive-RGB
I will go in detail about what is the purpose of each sub-project in the later sub-sections.
Time to get our hands dirty:
Since we got some background on what our goals, now let us go ahead and start creating the angular workspace:
Source code for our sample beehive made up beehive project can be found below:
- Install Angular CLI using NPM
npm i @angular/cli -g
2. Confirm Angular CLI is installed successfully
ng --version
3. Create Angular Workspace ng-beehive-monorepo
ng new ng-beehive-monorepo --create-application false
4. Create all sub-application(s)
//create beehive-RGB sub-applciation
ng g application beehive-RGB --routing --style=scss//create beehive-RG sub-applciation
ng g application beehive-RG --routing --style=scss//create beehive-red sub-applciation
ng g application beehive-red --routing --style=scss//create beehive-green sub-applciation
ng g application beehive-green --routing --style=scss//create beehive-blue sub-applciation
ng g application beehive-blue --routing --style=scss
5. Create Library project(s):
//Create library project lib-beehive-RG-shared
ng g lib lib-beehive-RG-shared//Create library project lib-beehive-UI-shared
ng g lib lib-beehive-UI-shared//Create library project lib-beehive-RGB-shared
ng g lib lib-beehive-RGB
Now that we have successfully created an angular workspace with multiple sub-projects before we proceed any further, lets us quickly go through some quick rules associated dependency hierarchy.
Project Dependency and Code sharing rules between projects:
As mentioned previously, the Monorepo pattern allows us to keep multiple applications code into one single repository which has its own pros and cons. Although it is a really big topic to discuss, I would highly recommend reading the Angular Enterprise Monorepo pattern by Victor Savkin and his team which details advantages and limitations. They also developed an awesome library called NX which allows us to apply advanced techniques to build large scale enterprise-level angular applications that adhere to monorepo patterns. That being said, in our made-up beehive project, here are some of the dependency hierarchy rules that I came up:
- Components/modules/directives/pipes etc., that are declared in standalone projects
beehive-red, beehive-blue, beehive-green
shouldn’t be imported directly instead rely on the lib-projects which allows us to avoid circular dependency. This may be completely defiant from the whole concept of monorepo pattern but I felt it makes life a little easier if we have certain rules and standards set in place from making the applications dependency hierarchy over-complicated over the period of time. Also, isolating the standalone sub-projects from each other allows multiple teams to work on these projects independently along with the added advantage of shipping set functionality or feature as one small independent application.
2. Use integration sub-project to combine functionality from the various standalone project by leveraging the Angular Router to lazy load the modules or webpack import() for components respectively.
3. All the UI components which are primarily a presentational component(eg., Multi-select Combobox, Grids, reusable form elements, etc.,) and don’t have business functionality (a.ka. dumb components) should be added as part of lib-beehive-UI-shared
4. Any of the core app-level functionality which may be used in all standalone or integrational projects like loggers, base classes, directives, functional feature components, which may be useful should be part of lib-beehive-RGB-shared.
5. Any external libraries eg., Angular Material, ng-bootstrap, or ag-grid may be added as part of lib-beehive-RGB
and exported via a module defined in lib-beehive-RGB
. It may not happen quite often but in a situation where the support for the library is no longer available or there is a reason to migrate to a different library, it becomes a nightmare to identify all the references and change accordingly if all sub-projects import them directly instead of channeling it from in house lib project. I feel it is even better if we could write a wrapper around the external library component and use this instead of directly referencing. This allows us to migrate to a newer or different library without impacting enter application(s). However, writing wrappers is not always a feasible option but its a design decision to be made as a team.
6. If there are any business-specific functionalities that are needed to be shared only between a subset of sub-projects (e.g., beehive-green and beehive-red ) then we can use a Library project similar to lib-beehive-RG-shared
Now that we got some idea about the dependency hierarchy and rules to follow, let's go ahead and see how we can run these projects independently:
update the package.json to add these commands:
"start:beehive-blue":"ng serve --project=beehive-blue --port 4000","start:beehive-red":"ng serve --project=beehive-red --port 4100","start:beehive-green":"ng serve --project=beehive-green --port 4300","start:beehive-RGB":"ng serve --project=beehive-RGB --port 4400","start:beehive-RG":"ng serve --project=beehive-RG --port 4500"
To start an application eg., beehive-blue and the console execute below command
npm run start:beehive-red
As mentioned in the above console logs, you can use http://localhost:4100 to launch the application in the browser. At this moment you will see the static default template content that angular ships when creating the application project via CLI.
Let's go ahead and add some modules and routable components in beehive-red project:
//create module bee-red-happy along with routing
ng g m pages/bee-red-happy --routing --project=beehive-red//console ouput
CREATE projects/beehive-red/src/app/pages/bee-red-happy/bee-red-happy-routing.module.ts (255 bytes)
CREATE projects/beehive-red/src/app/pages/bee-red-happy/bee-red-happy.module.ts (302 bytes)//create bee-blue-happy component
ng g c pages/bee-red-happy --project=beehive-red//console output
CREATE projects/beehive-red/src/app/pages/bee-red-happy/bee-red-happy.component.html (28 bytes)
CREATE projects/beehive-red/src/app/pages/bee-red-happy/bee-red-happy.component.spec.ts (665 bytes)
CREATE projects/beehive-red/src/app/pages/bee-red-happy/bee-red-happy.component.ts (302 bytes)
CREATE projects/beehive-red/src/app/pages/bee-red-happy/bee-red-happy.component.scss (0 bytes)
UPDATE projects/beehive-red/src/app/pages/bee-red-happy/bee-red-happy.module.ts (388 bytes)
Update the routes to add a mapping between /beehive-red-happy path to BeeRedHappyComponent
const routes: Routes = [
{path:'',component:BeeRedHappyComponent}
];
Update AppRoutingModule as below to lazily load modules. This allows us to run the beehive-red sub-project as a standalone application
const routes: Routes = [{"path":"pages/beehive-red-happy",loadChildren:()=>import('./pages/bee-red-happy/bee-red-happy.module').then(mod=>mod.BeeRedHappyModule)},{"path":"",redirectTo:"pages/beehive-red-happy",pathMatch:'full'},{"path":"**",redirectTo:"pages/beehive-red-happy",pathMatch:'full'}
];
Now that we were able to launch the application, let's go ahead and implement a simple presentation component. Since this is a presentational or UI component and can be re-used in almost all projects within the ng-beehive-monorepo
, we can add this as part of lib-beehive-ui-shared
Here’s what our presentation component takes in as the input and renders a simple HTML content:
Input: Component Name Router URL pathName of the Module that the component belongs toName of the project that component name and module it belongsOutput:Hello,I am {{ComponentName}} with Router URL path as {{urlPath}} and belong to {{module}} of {{project}}//CLI Command:
ng g m features/component-identifier --project=lib-beehive-UI-shared//Console output
CREATE projects/lib-beehive-ui-shared/src/lib/features/component-identifier/component-identifier.module.ts (205 bytes)//CLI Command:
ng g c features/component-identifier --project=lib-beehive-UI-shared//Console output
CREATE projects/lib-beehive-ui-shared/src/lib/features/component-identifier/component-identifier.component.html (35 bytes)
CREATE projects/lib-beehive-ui-shared/src/lib/features/component-identifier/component-identifier.component.spec.ts (720 bytes)
CREATE projects/lib-beehive-ui-shared/src/lib/features/component-identifier/component-identifier.component.ts (331 bytes)
CREATE projects/lib-beehive-ui-shared/src/lib/features/component-identifier/component-identifier.component.scss (0 bytes)
UPDATE projects/lib-beehive-ui-shared/src/lib/features/component-identifier/component-identifier.module.ts (314 bytes)
After adding the appropriate code in the component, here is how it looks: (see GitHub link for source code). Pardon my presentation skills, I didn’t want to spend too much time on making the UI look aesthetic, as my main goal was to give some idea how multiple-subprojects grouped together and maintain the code in one single repository.
After repeating the same approach for all the standalone and integration sub-projects. Here is how it looks when you run all the sub-projects independently.
Finally, let’s see how we can make a prod build for all these sub-projects individually:
You can update package.json with below command:
"prod:build:beehive-blue":"ng build --prod --project beehive-blue --base-href /beehive-blue","prod:build:beehive-green":"ng build --prod --project beehive-green --base-href /beehive-green","prod:build:beehive-red":"ng build --prod --project beehive-red --base-href /beehive-red","prod:build:beehive-RG":"ng build --prod --project beehive-RG --base-href /beehive-RG","prod:build:beehive-RGB":"ng build --prod --project beehive-RGB --base-href /beehive-RGB"
executing below command in the CLI will generate artificates for the prod deployment:npm run prod:build:beehive-blue
All the code for this project can be found below:
Summary:
To summarize we walkthrough:
- How to create an angular workspace with multiple sub-projects which are categorized into standalone, integration and library sub-projects
- We went through the list of CLI commands to generate the sub-projects and ways to generate the reusable UI components in library projects.
- Discussed the project dependency hierarchy and some rules like which project should be isolated and where to create the reusable components.
- How to run the applications independently using the same angular workspace and also CLI commands for generating production artifacts for each sub-projects separately.
This was a very long post for me, I hope you guys liked it. Please do clap and share it with your friends and colleagues. If there any questions, anything is wrong or needs modification, please do comment.
Resources:
1. All the images were generated using subscribed Microsoft PowerPoint. Icons and stickers images were part of the PowerPoint’s shapes, icons.
2. https://angular.io/guide/workspace-config
3. https://go.nrwl.io/angular-enterprise-monorepo-patterns-new-book