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
.
This will give us the usual Angular CLI default screen when we access localhost:4200
.
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.
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.