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-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
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:
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
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
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
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
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
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:
<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
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 :-)
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!