When companies projects become large it ends up splitting into several sub-projects. It's a bit like micro frontends, the trendy word for microservices in the browser.

In both cases, you got many projects and want to share some common behaviour. That's why you begin to create libraries to share common things such as logging and API services.

In this article, you'll learn how to create a library with Angular. The Angular documentation is complete about how to create a library. Yet, it's hard to grasp if you're not confident with some basics. While creating your library, you'll also learn about dependencies linking and peer dependencies.

Create your library

Let's use ng-cli, it's made for this. That's the tool which can generate, build, run and deploy Angular projects.

$ npm install --global @angular/cli

$ ng new my-lib --create-application=false

$ cd my-lib
$ ng generate library my-lib
$ ng build

By default ng new creates an app. If you create a library after hand both the app and the library will share the same repository. This isn't convenient if you publish the library on an npm registry. When other teams want to change the library, it'll be hard for them to find out the right repository.

For this reason, the first step is to generate a workspace without any project. Then, you generate the library with a second command. If you're curious about created files, take a look at Workspace and project file structure documentation.

Adding dependencies

Now, you have your library. Let's say you need to use lodash concat function. I know JavaScript arrays have a concat method. The truth is others lodash dependencies were already installed with Angular.

Be careful, the workspace directory contains a package.json. It helps to share dependencies across workspace projects. I don't recommend using this one. Prefer declaring your library dependencies into your library package.json.

$ cd ./projects/my-lib
$ npm install lodash.concat

Go ahead and add lodash.concat. Now import and use it in my-lib.service and build the library.

import concat from 'lodash.concat';

@Injectable({ providedIn: 'root' })
export class MyLibService {
  doSomething() {
	// Make sure tree shaking won't remove the lib during the build
	console.log(concat([1], 2))
  }
}

Angular doesn't agree, it shows a warning. Ignore the error message, whitelisting isn't a good practice.

Prefer to declare lodash.concat as a peer dependency instead, this is the warning piece of advice. But why is that? What's a peer dependency?

Actually a peer dependency is like saying: "I need this, be sure it'll be available when needed". Think about it, the library can't run on its own, it runs within an app which uses it.

This is the app job to provide the dependencies its libraries needs. The main reason is to avoid having the same package in the app and in the library with a different version. It'll for sure bring some Heisenbug and incompatibilities.

{
  "name": "my-lib",
  "version": "0.0.1",
  "peerDependencies": {
    "@angular/common": "^7.2.0",
    "@angular/core": "^7.2.0",
    "lodash.concat": "^4.5.0"
  }
}

Let's declare lodash.concat as a peer dependency instead. Open the library package.json and move it from dependencies to peerDependencies section. Also, delete node_modules/lodash.concat because the app will provide it for us. What? The build is failing because lodash.concat module can't be find?

Get back to the Angular documentation, the part about peer dependencies will help us. Here is what it explains:

While developing a library, you must install all peer dependencies through devDependencies to ensure that the library compiles properly

Fine, add lodash.concat in dev dependencies and try to compile again. It's working, everything is green! That's it, library dependencies must be in both dev and peer dependency at the same time.

$ npm install lodash.concat --save-dev

$ ng build
$ ng test

Publish your library

Look at the terminal when building the library. It details the Angular package is build from your source code to a dist directory.

Inspect the package.json in the build output. The version is 0.0.1, it's the library version. It means it's the library package.json, not the one in the workspace with version 0.0.0. Also note, the dev dependencies part is missing, only peer dependencies are important.

To publish your package on a registry, run npm publish. Be sure you configured npm to use your company registry if needed. This command creates a tarball from your compiled library (like npm pack). Then, it uploads this tarball to the configured registry.

The key is to publish from your compiled library directory (dist). Don't publish from your library codebase in projects/my-lib. The last step to use your library is to run npm install my-lib in an Angular application. The name of the library is the one defined in the package.json.

Create an app using your library

Again, let ng-cli do the job:

$ ng generate my-app
$ ng build

Well done, the app can build. The next step is to use our library. Not the copy from the registry (run npm install my-lib) but the local one. It's useful you need to update the library and if it works well in your app.

There is a command for this: link. Like publish, you must run it from your library dist directory. It'll create a symlink in npm local registry pointing to your compiled library.

$ cd ./dist/my-lib
$ npm link

$ cd my-app
$ npm link my-lib

The last step is to make your app using the npm local registry for your library. Run npm link again but specify your library name. Check the node_modules for your app. You must see my-link which a symlink pointing to your library dist directory.

Let's use our library in the app component.

import { MyLibService } from 'my-lib';

@Component({ /* ... */ })
export class AppComponent {
  constructor(myLibService: MyLibService) {
	myLibService.doSomething();
  }
}

When building the app it should show some errors. It can't resolve lodash.concat in some file from my-lib.

Does it remind you something? Yes, lodash.concat is the peer dependency, we declared in the library. As explained before, it's the app job to provide libraries peer dependencies.

$ npm install lodash.concat

Still not working? It works when the library isn't linked. To make it work for the link as well, let's dig again in Angular documentation.

Use TypeScript path mapping to tell TypeScript that it should load some modules from a specific location. List all the peer dependencies that your library uses in the workspace TypeScript configuration file ./tsconfig.json, and point them at the local copy in the app's node_modules folder.

The paragraph before describes our situation: link a library having peer dependencies in an app. Let's edit this tsconfig file to ensure the library is using dependencies provided by the app.

{
  "compilerOptions": {
    "paths": {
      "lodash.concat": [
        "./node_modules/lodash.concat"
      ]
    }
  }
}
my-app/tsconfig.json

Run ng serve to check if the console.log using our library is working. Congratulation, you did a good job!

At this point, you should be able to remove the node_modules in projects/my-lib. This way you're sure the library uses app dependencies. Don't do it, read the last part before, it contains a small team you want to try.

Bonus

Here is a bonus for you, we'll use a small tip included at the very end of the Angular documentation.

The goal is to edit the library source and see the live result in the app using a watcher. No need to build again the library and link/unlink it in the library dist and the app.

$ cd my-lib
$ ng build --watch

$ cd my-app
$ ng serve
$ firefox http://localhost:4200

Stop your app server before running ng build with watch option. You can't do it in dist directory, use the workspace root for instance. Run again your app server and change code in your library service. It rebuilds your lib and the app recompile because it detected changes in its node_modules.


Wrapping up

Thanks for reading! I hope it helped you to create an Angular  library and make it work in your app. There were already much information in Angular documentation. Yet, it was hard form me to use it well at first.

The documentation will evolve with future angular version. Now you have the basics to understand it on your own. I hope it helped you, thanks for reading!

Don't hesitate to share your experience in the community section.