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