MonoRepo + Angular Packaged Libs :: You Can Have Your Cake and Eat It Too!!

You can have a MonoRepo and Packaged Angular Libraries at the same time!!!

I finally had some time to check out the NRWL.io Nx Workspace tool to create an Nx Workspace (MonoRepo…See article by Alex Eagle) and the ng-packagr tool to package libraries using the Angular Package Format version 6. Tools and process: a good combination.

The reason why I want to document the changes and the configuration for an Nx Workspace is that I have (4) libraries and at least (1) CLI web project that I want to configure in the new Nx Workspace environment. My WIP at my day job has (5) CLI web projects and (4) custom shared libraries. I want to be able to bring all of these projects and libraries into the workspace and understand the changes. Here's my goal:

  1. Demonstrate the ability to have multiple applications and libraries in a single workspace.
  2. Use a simplified workflow to allow consuming applications to reference libraries via @scope.
  3. To allow for the custom libraries to be packaged using Angular Package Format conventions via ng-packagr.
  4. Use semantic versioning with the ability to publish to NPM.
  5. The ability to consume the NPM packaged versions for application deployments.

Install the Nx Workspace tools/packages globally. You get the Nx Schematics to create a MonoRepo workspace.

npm install -g @nrwl/schematics
npm install -g @nrwl/nx

Create a new workspace:

EX: create-nx-workspace buildmotion-angular-workspace --npm-scope=buildmotion
create-nx-workspace <YOUR-WORKSPACE-NAME> --npm-scope=<YOUR-SCOPE-NAME>

Create a new project

EX: ng generate app angularlicious-web
ng generate app <CONSUMING-NG-APP-NAME>

The generation will create the following new files.

create apps/angularlicious-web/e2e/app.e2e-spec.ts (282 bytes)
create apps/angularlicious-web/e2e/app.po.ts (201 bytes)
create apps/angularlicious-web/e2e/tsconfig.e2e.json (404 bytes)
create apps/angularlicious-web/src/assets/nx-logo.png (71592 bytes)
create apps/angularlicious-web/src/assets/.gitkeep (0 bytes)
create apps/angularlicious-web/src/environments/environment.prod.ts (51 bytes)
create apps/angularlicious-web/src/environments/environment.ts (387 bytes)
create apps/angularlicious-web/src/favicon.ico (5430 bytes)
create apps/angularlicious-web/src/index.html (303 bytes)
create apps/angularlicious-web/src/main.ts (370 bytes)
create apps/angularlicious-web/src/polyfills.ts (2667 bytes)
create apps/angularlicious-web/src/styles.css (80 bytes)
create apps/angularlicious-web/src/tsconfig.app.json (307 bytes)
create apps/angularlicious-web/src/app/app.module.ts (342 bytes)
create apps/angularlicious-web/src/app/app.component.html (520 bytes)
create apps/angularlicious-web/src/app/app.component.spec.ts (607 bytes)
create apps/angularlicious-web/src/app/app.component.ts (258 bytes)
create apps/angularlicious-web/src/app/app.component.css (0 bytes)
update .angular-cli.json (1705 bytes)

Application Project Files

Notice that the new CLI project doesn’t have all of the project-based files. The project-based files are shared by all CLI projects in the apps folder. They are located in the root of the workspace. This simplifies things a bit.

.angular-cli.json

The .angular-cli.json file is updated with (2) new lint definitions.

{
"project": "apps/angularlicious-web/src/tsconfig.app.json",
"exclude": "**/node_modules/**"
},
{
"project": "apps/angularlicious-web/e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**"

The apps collection also gets an object configuration for the new CLI project. All CLI projects and even library projects will be added to the apps collection in the package.json file.

Note that the tsconfig.app.json extends the tsconfig.json in the root of the Nx Workspace.

The complete .angular-cli.json file:

Another difference between Angular CLI projects and the Nx Workspace project is in the AppModule of the application. The module imports NxModule from @nrwl/nx. It is added using the @NgModule imports collections with a .forRoot() call to initialize the configuration of module.

So, if you are moving in an existing CLI web project, know that you will have to add the NxModule to the AppModuleconfiguration.

Custom Library

Before, I move any of my existing CLI web projects. I’m going to add a custom library to the libs folder to see the Workspace changes. The simplest library/module I have is my logging module. I'll start with this.

Add a new library (details). Note, that I am not including buildmotion in the name of the library. This is because our workspace has a @scope defined as @buildmotion. Therefore, we want to reference our package by @buildmotion/logging in the consumer of the library.

Ex: ng generate lib logging --parentModule=apps/angularlicious-web/src/app/app.module.ts
ng generate lib <NAME-OF-LIB> --parentModule=apps/<NAME-OF-CONSUMING-APP>/src/app/app.module.ts
Note: If your library requires routing, use:
ng generate lib mymodule --routing

The generation process created/updated the following files. When the mdoule is referenced in a consuming application the name of the module by default is LoggingModule. I like to brand that a little more, I'm going to update the module class name to BuildMotionLoggingModule - this allows for the module to have a more distinct name (I know where it is from) in the consuming apps.

create libs/buildmotion-logging/index.ts (77 bytes)
create libs/buildmotion-logging/src/logging.module.spec.ts (215 bytes)
create libs/buildmotion-logging/src/logging.module.ts (174 bytes)
update .angular-cli.json (1873 bytes)

Take a look at the .angular-cli.json changes. The apps collection gets a new object configuration for the new lib. It is interesting that it is configured as an app.

{
"name": "logging",
"root": "libs/logging/src",
"test": "../../../test.js",
"appRoot": "",
"tags": []
}

An empty index.ts file is created to allow you to add items you want to expose publicly to your library consumers. Update this file to export all items that should be available to your library consumers. Currently, this entry point is named index.ts- however, the current Angular Package Format v6 suggests the name should be public_api.ts by convention. This file's name may be updated by the Nrwl.Nx team to align with the APF suggested name.

export { BuildMotionLoggingModule } from './src/logging.module';

Using the Custom Library

You can now import the new BuildMotionLoggingModule module into the consumer application.

import { BuildMotionLoggingModule } from '@buildmotion/logging';

Legacy Workflow

Note: You no longer have to do an [npm install] for your custom package. It is referenced using the @buildmotion scope from within the Nx Workspace. This is a nice benefit of the Nx Workspace — the development workflow is now much faster. Before, you would have to:

  1. update the module/library code
  2. update the version of the library (semantic versioning)
  3. compile/build
  4. publish to a package manager (or local private repository).
  5. update the package version dependency in the consuming application.
  6. test and verify the changes work
  7. repeat if there are any issues/problems and/or updates to the shared library.

Nx Workspace Workflow

The Nx Workspace workflow is much more streamlined and efficient for development of custom/shared libraries with the consuming applications.

  1. update the module/library code
  2. test and verify the changes work in the consuming application
  3. repeat if there are any issues/problems and/or updates to the shared library.

The updated AppModule now looks like the following. If the logging module had something we could actually use, we would be ready to consume it in the application's components.

Package Your Libraries for Distribution

So, we now have a MonoRepo. I wasn't aware until recently of the many advantages of a MonoRepo - but, there are also a few drawbacks. It appears that the default configuration of the Nx Workspace is not setup to publish your libraries as packages. One of the reasons I'm setting up this workspace is to learn how this can either benefit or hinder my development team. A MonoRepo may not be suitable for every solution. However, having another tool and development workflow that can simplify things and still allowing for publishing custom libraries is pretty nice.

It isn’t setup by default. However, we can use the existing configuration elements and tools like ng-packagr to create libraries ready for publishing. Use the following command to add the development package to the workspace package.json file.

npm install --save-dev ng-packagr

Configure ng-packagr

Add a new package.json file to the root of the target lib. This is where we will define the ng-packagr configuration for the build and destination location. More information.

  • $schema: used to reference ng-packagr specific properties in the configuration file.
  • name: the name of the library.
  • version: the semantic version of the library. Use npm version major|minor|patch commands to update the version of the library.
  • ngPackage: an object value for:
  • lib: an object value that contains the entryFile to indicate the entry file of the library (contains the manifest of exported items)
  • dest: use to indicate the destination of the packaged output for the library.
  • repository: provides the source code repository for the package.
  • keywords: used by the package manager
  • license: use to indicate the license type.
  • peerDependencies: used by Angular consumers of the package to determine required peer dependencies.

Now, that we have some ng-packagr configuration setup, we can build the library and prepare the package for publishing.

npm run build:libs

The ng-packagr build process provides nice details in the output.

Output from ng-packagr to dist folder.

The destination folder indicated in the lib’s package.json file puts the output in the workspace's dist folder under the scope name @buildmotion.

Library Packaged for Angular (esm5), UMD, and esm2015.

Workspace Configuration package.json

Following the tenents of MonoRepo, we have a single-source of truth for the definitions and build scripts for the workspace. We will update the package.json in the root of the workspace to include (2) new scripts that target the build of our custom libs.

"build:libs": "npm run build:buildmotion-logging",
"build:buildmotion-logging": "ng-packagr -p libs/logging/package.json",

When we have multiple libs that we want to build, we can use the single build:libs script that coordinates the build of all other libs.

"build:libs": "npm run build:buildmotion-logging && npm run build:lib-this && npm run build:lib-that ",
"build:buildmotion-logging": "ng-packagr -p libs/buildmotion-logging/package.json",
"build:lib-this": "ng-packagr -p libs/lib-this/package.json",
"build:lib-that": "ng-packagr -p libs/lib-that/package.json",

Logging

I updated the module with a service, a configuration (future), and an enum to indicate the Severity of the log item. This will provide some basic functionality that we can use from any of the workspace apps.

The enum indicates the severity of the log message.

Consume the Tacos (Lib)!

Since, we now have a library — we can start to consume it. The target lib is a logging service. We can add the LoggingServiceto the providers collection of the AppModule.

providers: [
LoggingService
]

Now that the service is provided — basically, now available through Angular’s Depdendency Injection mechanism. We can add the LoggingService to the constructor of the AppComponent. Now it is available for use in the class. Note the usage in the ngOnInit method.

Run the Application

You can create a launch profile - and update the port to use 4200.

Now we can serve up the application (ng serve) and launch the debugger (F5).

ng serve

Conclusion

I think I’m having a cake and eat it too moment. Wow, I can have a MonoRepo Workspace environment with multiple applications consuming multiple libraries. I can also publish those packages to NPM with semantic versioning — so that they can be shared. The production environment configuration of the application can be configured to consume the distributed version of the module package.

Now, how about some ice cream, err, I mean tacos!

Angular CLI App consuming Nx Module

For more information about Matt Vaughn and the Angularlicious Podcast go to http://angularlicio.us.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store