How to build a library for Angular apps?

The complete, step-by-step guide

One of the things that Angular team made ridiculously easy with the introduction of Angular 6 is a possibility to create Angular libraries. If you’ve ever tried to do it before, you know that it was hardly a simple endeavour…

How does the process look now? Let’s follow a typical scenario: we are going to create a library that provides a service, a component and some interfaces.

We will be dealing with a topic close to many hearts: TV shows. The library will work with a data provided by tvmaze.com api, so we’ll name our library tvmaze.

1. Create the library project

We start as usual, by generating the initial setup with the ng new command:

ng new lib-demo --prefix ld

With the version 6 of Angular CLI the format of configuration file changed in a pretty substantial way. angular.json represents what is called a workspace now, with possibly multiple projects. By default we get lib-demo and lib-demo-e2e projects generated for us, but you can add more with "ng generate application [my-app-name]" command.

What you can also generate instead of application, is a library:

ng generate library tvmaze --prefix tm

As for the flags for this command, there are several of those options, but setting up prefix is the absolute minimum. If you won’t choose prefix it will default to lib-.

The generate command did several changes to our app:

Most notably, a new "projects" directory was created, with "tvmaze" folder inside. This new project is referenced in our angular.json configuration too.

2. Define and provide some interfaces

An api request will look like this: https://api.tvmaze.com/shows/336 . 
If you follow the link you will notice that the returned json is pretty complex — it would be a good idea to define a reusable set of interfaces and provide them to our library’s users. You can use a wonderful jsontots.com tool to do the former:

Just copy the api’s response, paste it in to field on jsontots.com site and voila! You have the interfaces! Btw. for more similar tips follow my @sulco twitter account :-)

Let’s create a projects/tvmaze/src/lib/tvmaze.models.ts file with all the interfaces. There’s bunch of them and they are all pretty closely related so it really doesn’t make sense to create a separate file for every one of them.

To allow developers using this library to have an access to our interfaces, we need to provide them as a public api of the tvmaze library. Edit the generated in the 1st step projects/tvmaze/src/public_api.ts file so it looks like this:

3. Create a service in the library

Our newly created tvmaze has its own package.json, tsconfig.json, tslint.json and karma.conf.js since it makes sense that it may need a different setup than the main application. Is also has a sample component, module and a service generated for us. We can also add additional ones, which we will do in a moment.

For now let’s add some logic to the projects/tvmaze/src/lib/tvmaze.service.ts:

There is nothing unusual in this service other than the provideIn: root configuration in the @Injectable decorator. It’s actually a new setting added in Angular 6.

"provideIn: root" allows to provide a service without explicitly registering it in any NgModule.

Why is that useful? It enables such service to be tree-shaken (removed form the production bundle if not used), which is a common scenario in case of services defined in libraries.

4. Create a component in the library

Our library will allow to display a poster of given show. As with a typical Angular project, you don’t have to create a component by hand — it’s what the CLI is for:

ng generate component poster --project=tvmaze

We just needed to tell the CLI to create the poster inside our tvmaze library.

To make the component available outside the lib’s tvmaze.module, we need to add it to the exports section. Make sure your projects/tvmaze/src/lib/tvmaze.module.ts looks like this:

I’ve also added HttpClientModule dependency, since we needed HttpClient in TvMazeService, and CommonModule because it is where async pipe and ngIf directive (that the poster component uses) are defined.

5. Build time!

Before we can use our library, we need to build it. Again, Angular CLI helps with that. We can now tell it to build a specific project:

ng build tvmaze

6. Use the library

To try our library out, we need to do what we always would with 3rd party extension — define as a dependency in the imports section of the app’s NgModule:

The thing to note here is that we don’t import the class by a relative path to the tvmaze directory, but do it as if it was already in node_modules. It it actually there? Nope.

So how does this work?

When we’ve generated the library (ng generate library tvmaze ) Angular CLI modified the tsconfig.json in the root of our project by adding tvmaze to the paths entry.

This means that whenever in your TypeScript code you import something from "tvmaze" it is actually being imported from the dist/tvmaze directory, where the build of our library has been saved.

That is really convenient, because once you publish the library to the actual npm repository and want to start using that version, you won’t have to change any imports in the source of the application — only remove that paths entry.

Now let’s display something.

Let’s modify the default src/app/app.component.ts to look like this:

Here we are using everything that our tvmaze service provides: 
the Show interface, TvmazeService and <tm-poster>. Once we we start the app, this should be a result:

7. Publish the library

To make the library available on npm all you need to do is create a production build, and run npm publish command from the project’s dist directory:

ng build tvmaze --prod
cd dist/tvmaze
npm publish

If you haven’t published anything before, you will need to create an npm account first and log in (see the details in the docs), but otherwise… 
that’s it :-)

Epilogue

What we didn’t cover in the guide is testing — that itself is a topic on its own, but other than running ng test tvmaze command instead of just ng test it is like testing any other Angular app.

One thing missing to perfection in the current setup is a bit better automation: for now every time you want to see in your app changes that you’ve introduced in the library, you need to rebuild the library, and often restart the main app’s server. Angular team is working on making this more streamlined, but in the meantime what I can suggest is to make a script that you assign a key binding in your IDE to (e.g. use Visual Studio Code’s tasks). That’s what I’m doing.

That being said, the current process is already such a great development experience that I’m only waiting for a bunch of new libraries popping up all over the ecosystem :-)

Btw. as always, you can check the finished code used in this article on GitHub. And make sure you read the wiki entry on CLI’s repository for additional information like the reasoning behind certain architectural choices.

Did you learn something new? If so please:

→ clap 👏 button below️ so more people can see this
follow me on Twitter (@sulco) so you won’t miss future posts!