This page looks best with JavaScript enabled

Angular - Enterprise Library Workflow

 ·   ·  ☕ 6 min read

In this workshop, we will create an Angular library, publishable to NPM, with multiple entry points, unit tests and CI/CD in GitHub. Source code for each step will be provided, as well as complete solution in a GitHub repo. Licensed under MIT, use it however you wish. You can skip the intro and see the steps here.

Why this article?

A little introduction into why I think an article like this is necessary and why existing internet resources are not enough. When I was researching this topic, there were a lot of diverse opinions about the best way to do this, each going in a different direction. Some suggest opening an existing GitHub code such as PrimeNG and copying what they did there. The problem - it is often done with hacks and against Angular’s guidelines in regards to where things should be and how they should work.

Angular library generation CLI also has an issue, because it links the library via dist folder, where my IDE has a problem with intellisense, and so it fails to produce pretty import links.

Finally, I checked NX and while it is possible to create the intended result with it - and it’s fairly simply with only a handful of steps, using NX adds unnecessary complexity to the application. So unless you need to add hundreds of entry points, and/or have hundreds of developers working on the same project, then you probably don’t need it. NX also has Jest as the default test runner. Turns out Jest does not support visual test debug. It also requires use of 3rd party plugins for almost anything useful. They probably had a good reason for this choice, but I still prefer Karma as my test runner.

In this article I will describe how to solve all of these problems and a few others that will come up along the way, using minimal changes to the CLI generated boilerplate. Minimal changes means it is impossible to make fewer changes and still end up with the same result. Not as a challenge to the reader, but rather a promise of quality here.

Also regarding quality, we will pretend that our code will be used in an enterprise environment and so despite having only a few components with one unit test each, we need to prepare for long term maintenance of it.

Getting Started

I decided to break it down into a series of simple steps. Each code change is involved is clearly marked with a valid commit hash which links to an actual GitHub commit.

  1. #7aa32855 Generate new app using Angular CLI.
    ng new YourAppName

  2. #31d0f527 Remove all HTML inside of app.component.html and add a few lines in a shape of Hello World. I added this:

    1
    2
    
    <div>Hello, NPM lib!</div>
    <div>Future common component</div>
    
  3. Test your changes in http://localhost:4200, this ensures your development setup works, before we get further. Here and later I will assume you are using IntelliJ Rider or any of Jetbrains IDEs such as WebStorm.

  4. #831dc672 Use Angular CLI to generate a library inside your main app.
    ng generate library my-lib
    Then add build:lib script in package.json. This will generate production output that you can publish to NPM.
    "build:lib": "ng build my-lib",

  5. #f47230a9 Remove the default component inside a library generated by the CLI, and use CLI to generate a new component we will be using. Don’t ask me why, but each of this approaches produces a different file structure and slightly different code.
    We also need to update public-api to export our new component and update tsconfig to make our library available via NPM import path when using a local file reference. This way there is no difference from using an external NPM package, as far as our code is concerned. I will explain later why this is useful.

  6. #a60c4c75 Add a secondary component using Angular CLI, export from public-api and start using in the main app.

  7. #c0bea754 Move secondary component inside the secondary entry point inside my-lib, adjust tsconfig and main app’s imports.

  8. #5177f6b2 Fix unit tests, add ng-mocks, update test boilerplate, add CI headless run script, and IntelliJ run configs for running main app’s tests, as well as library unit tests.

  9. #9ccd864c Add GitHub workflows to run unit tests and build library as part of PR process. We want to make sure our library is stable and these two steps are essential for this. Demo can be seen here:

PR checks - successful

  1. (optional) Remember in step 5 we imported our components as if using NPM via package.json? The whole app can be built in hot reload mode via npm run start, including both the library and the demo app. We could use a file reference in the import. Here is why we did not. First, build your library if you haven’t already. Next, open tsconfig and comment out paths starting with projects/, then uncomment those starting with dist/.

    Now build the main project. It should build the same way, but now you are using the actual production binaries that would be downloaded from NPM. But wait, there is more. If you now publish to NPM, you can comment out the paths in tsconfig and start using the library from NPM, all without touching the imports in the source files. This enables testing at various deployment stages.

Realistically though, you only need to test that your library builds a prod bundle, and that your main app builds without errors. This covers 99.9% of errors. It is not sufficient to just build the main app, even though it includes the library portion in hot reload mode. This is because a dedicated library build will perform additional checks such as verifying no cycles on the dependency graph - if you have multiple libraries or entry points linking to each other. It also builds all entry points, regardless of what is actually used. This helps ensure a valid package.

Conclusion

Yep, this is it, we are done here! If you didn’t start your research on this topic, hopefully this saves you as little as a few hours and as much as a couple of days. This tutorial uses a recent version of Angular, v14, so it should be up-to-date for a couple of years at least. If not, send me an email, I’ll see how to make it valid in future versions.

Out of Scope

The following topics are out of scope for this article.

  • OnPush change detection. All components are implement using the default change detection strategy. OnPush is better for performance, but since performance is not the goal here, I did not bother changing. Always use OnPush. Easter egg here - all OnPush links are different, and each is a good starting point, pick the one you like.
  • How to create deployment previews for your PRs, where each commit is deployed in a staging environment and can be tested before merging to your production branch. You can use Netlify to accomplish that. It is fairly straightforward and can be done in 5 minutes if you know where to go.
  • How to get unit test runner or anything else covered in this article working in VS Code. I use IntelliJ Rider for an IDE.
  • Publishing to NPM, enterprise/team workflows and such. If you are okay with manual steps, increment your package version in package.json, then build, then go inside the dist/yourlibrary folder and publish it using npm publish. If you want something more advanced, check this out.

Victor Zakharov
WRITTEN BY
Victor Zakharov
Web Developer (Angular/.NET)