Building an Angular 4 Component Library with the Angular CLI and ng-packagr

Update (May 4th, 2018): As of Angular 6, the Angular CLI has official support for creating libraries! https://github.com/angular/angular-cli/releases

One of the best tools available for building Angular applications is the Angular CLI.

The CLI conveniently allows developers to create new projects that have testing, AoT, lazy loading, Typescript transpilation and linting preconfigured, and in doing so helps protect developers from the general pains of getting everything set up before they can get to coding.

But when it comes to building a component library, the CLI falls short — its immediate purpose is to help developers create applications, and applications have different requirements than component libraries.

The CLI team has indicated that component library creation is in the future for the CLI, but what if you need to build a component library now? Should you have to discard all the advantages offered by the CLI?

This post explains that with the help of a library called ng-packagr (https://www.npmjs.com/package/ng-packagr) by David Herges, the CLI and your component library can actually get along just fine.

Project goals

Let’s create a really, really simple component library. In it, we’re going to create a header component. Nothing too crazy here. But you’ll get the idea.

What would we benefit from here?

  • Auto-generation of the component library’s project folder
  • Auto-generation of the component library’s components, modules, and services
  • Auto-generation of the component library’s testing suite and assertions
  • Auto-generation of a sandbox that can be used to test out your components before packaging them
  • Auto-packaging of your component library into the Angular Package Format (https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs)

So where do we start?

Naturally, with the CLI. For the sake of completeness, let’s assume you don’t yet have the CLI installed. Go ahead and install it globally using npm. I’ll wait.

npm install @angular/cli -g

Great.

Now, let’s spin up our component library project folder.

ng new my-component-library

This should create a folder called my-component-library. cd into it and run ng serve.

ng-serve from within the component library project folder

This will give us the usual Angular CLI default screen when we access localhost:4200.

Default home screen of an Angular CLI project

Great, but that’s an app

Well, yeah. The CLI makes apps, remember? What we need to do is create components and find a way to export them into a packaged component library, not create an application.

But wouldn’t it be nice if we had a sandbox where we could test out our components before we package them? That’s what this is. Our sandbox. We’ll get to packaging in a bit, but humour me.

Create a component

Let’s use the CLI to generate a header component for us. We’ll use the selector app-header since that’s easier than being creative about it. And we’ll put it inside a folder called modules under a folder called header.

About feature modules

We’re going with this folder structure to reflect what we’re building. A component library is a collection of components, but a consuming application may not be interested in all of our components, and may not want to include the entire library in its bundle if it doesn’t need to. As a result, our component library is actually more like a collection of modules — modules that contain components. This type of module is known as a feature.

Create a header feature module/component

So let’s start with creating a feature header module that will contain the header component.

ng generate module modules/header

And then let’s create the component.

ng generate component modules/header

This will create the following files in our src/app/modules/header folder:

  • header.component.css
  • header.component.html
  • header.component.spec.ts
  • header.component.ts
  • header.module.ts

Make the header header-y

Let’s make the header do something awesome now. Let’s make it render a header!

Open header.component.html and replace its contents with the following:

<h1>
<ng-content></ng-content>
</h1>

Make the header beautiful

In header.component.css, let’s make sure that our header is appropriately red.

h1 {
color: red;
}

Export our component from our module

We know this is going to be a fantastic header, and it’s safe to assume that everyone is going to want to use it, so let’s make sure they can.

Open header.module.ts and add an exports array to the @NgModule block.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from './header.component';
@NgModule({
imports: [
CommonModule
],
declarations: [
HeaderComponent
],
exports: [
HeaderComponent // <-- this!
]
})
export class HeaderModule { }

This ensures that an application that imports our HeaderModule will actually be able to use the HeaderComponent. Without that exports array, the only components that would be able to use app-header in their templates would be other components inside the HeaderModule.

Take it for a spin

So let’s take a look at our header component before we package it up. To do so, let’s use our sandbox.

Open app.component.html and replace its content with the following:

<app-header>Such Header</app-header>

Here, we’re projecting the string “Such Header” into the <ng-content></ng-content> block we added to our header template.

Last step, we need to import our HeaderModule into our AppModule so that our app.component.html knows how to render <app-header>.

Add HeaderModule to the imports block of app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
// import our module 
import { HeaderModule } from './modules/header/header.module';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HeaderModule // import it into our @NgModule block
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Now if we run ng serve again and look at localhost:4200 we should see our header in all of it’s glory.

Called it!

Boring…..

Ok, ok, that’s not what you came for, I know. Let’s get to the good stuff.

Let’s share the love

So now we have an awesome component, wrapped up in an awesome feature module, and the world is banging down our doors desperate to get their hands on it. We love open source, so let’s let them have it. But how?

ng-packagr

ng-packagr is a node library that can compile and package a TypeScript library to Angular Package Format. We’re going to use it to pull our components out of this CLI project and package them up for us into a format that can be used within other Angular applications.

Run npm install ng-packagr --save-dev from the root of your project folder, this will download ng-packagr and declare it as adevDependency of your project in your package.json file, so we can call it from our npm scripts.

As per the ng-packagr docs, we’ll need to add two files to our project, ng-package.json and public_api.ts. We’ll use ng-package.json to configure n-packagr and to tell it where to find our public_api.ts file, which we’ll use to export the feature modules of our component library. (Note: public_api.ts is a convention used by Angular component libraries.)

Add the following to ng-package.json:

{
"$schema": "./node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "public_api.ts"
}
}

And export your header.module.ts from public_api.ts:

export * from './src/app/modules/header/header.module'

Now we’ll add a packager script to our package.json that we can use to tell ng-packagr to package up our library according to the configuration of ng-package.json. Also, switch private to false so that you can publish your library when you need to.

"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"packagr": "ng-packagr -p ng-package.json"
},
"private": false

Create our package

Now for the good stuff. Run npm run packagr, and once the process is complete you’ll find a dist folder in your project root. This is your component library. Fully self sufficient, packaged according to best practices, and ready to shine it’s red header-y light upon the world.

Pack it for local development

Let’s get a tarball setup that we can npm install from Angular applications within our local dev environments. cd into the dist folder and run npm pack. This will create a file in the root of the dist folder called my-component-library-0.0.0.tgz. The 0.0.0 part comes from the top of your package.json. You’ll want to keep bumping that up as you start to truly deploy your component library for consumption.

From other Angular applications on your system that require your component library, you can npm install ../some-relative-path/dist/my-component-library-0.0.0.tgz to install your component library into your application.

Publish it on npm

Once you login to your npm account with npm login you can publish your component library with npm publish dist. Just be sure that you have a unique package name (hint: my-component-library may be taken). Once published, you’ll be able to install your component library from npm with npm install my-component-library.

Consuming your component library

Once installed, you can import your header component into any application’s app.module.ts, by including it in its @NgModule imports array…

import { HeaderModule } from 'my-package-name';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HeaderModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

And using its selector in a template as you would a component that is part of your application.

Conclusion

Developing Angular applications has gotten exponentially easier with the release of the Angular CLI, and now, thanks to ng-packagr, we can extend the benefits of the CLI into the area of component libraries as well.

By considering your component library project folder an Angular application from which you export your component library, you can use the CLI to create components and their modules, serve up a sandbox for testing your components, and run tests on your components while reporting on their coverage.

Thanks to ng-packagr, it’s now possible to develop Angular Component Libraries using the same simple workflow afforded to us by the CLI.

Hopefully this works for you as well as it has for me. Feel free to reach me at @nikolasleblanc or comment below if you have any questions.