Creating A Simple Library Using Angular CLI 6

tudip-logo

Tudip

15 April 2019

The higher barrier in the Angular is creating libraries of components so that other Angular applications can use it too. With angular 6 it is much easier to create libraries so let’s dive right in..

We’ll cover:

  • Creating a library project with the Angular CLI
  • Building components for your library
  • Using your library in other applications
  • Publishing your library for others to use

Creating a library project with the Angular CLI

First, we need to setup the project. If you don’t have the latest version of the CLI install it from npm.

$ npm install -g @angular/cli@latest

Now let’s create a new project with the CLI.

$ ng new my-app

You may notice some changes in your project structure. The most noticeable change is that the old angular-cli.json is now replaced with a new angular.json file.

This file is the key to one of the new features in version 6. Now the Angular CLI can create and work with workspaces that contain one or more Angular projects. This file gives you control over the configuration of each of those projects. This also helps handle the building libraries differently than we normally would for normal Angular applications.

So now we know how libraries within CLI projects work. let’s create the library structure in your project. We will do this with the generate command just like we would to create a component, service, module, etc.

$ ng generate library my-new-lib

This will create a new /projects directory with a new folder and some example files for your library.

The main files which control the configuration of the ng-packagr library are public_api.ts, ng-package.json, and ng-package.prod.json. You must be thinking about what is ng-packagr, this is the library that powers the packaging of your library.

Let’s take a quick review of these files:

public_api.ts is our new entry point for your library. If you have any files that you want to be accessible to consumers of your library (modules, components, etc…) you need to export them here in addition to exporting them from whatever modules are in your library.

export * from './lib/my-new-library.service';
export * from './lib/my-new-library.component';
export * from './lib/my-new-library.module';

ng-package.json and ng-package.prod.json control the configuration for the packaging process which ng-packagr performs. You can use them to change the destination build directory or defining a different entry point for your app. ng-package.json is used during your ng build command and ng-package.prod.json is used when you run ng build –prod. The only difference between these two files right now is that ng-package.json contains a deleteDestPath flag that will delete your destination directory before running a build.

Building components for your library

Now, we have the project structure for our library setup. Let’s start building our library!

First, add Angular Material to our project.

$ ng add @angular/material --project my-app

But, why are we adding Material to my-app and not my-new-lib?. The answer is, in this case, Material is a peer dependency of our library. We don’t want this to be downloaded each time when our library is installed. Instead, we want to ensure that whichever project is using our library needs to also have Material installed. Hence you’ll need to add things like Material to the peer dependencies.

Additionally, the ng add command for Material only works for standard project configuration, i.e. those generated by the ng new command. If you were to run ng add @angular/material –project my-new-lib you would get an error saying so. When the schematic which run in the background to add Material assumes that you are adding it to an existing Angular app and not a library, so it will not understand since the structure inside angular.json that is set up for your library.

Now, lets add to our peer dependencies now.

"peerDependencies": {
"@angular/material": "^6.1.0"
}

It’s time to setup the module for our library. First, we need to delete the example files which were generated by the CLI in src/lib and from public_api.ts. Then create the module in the same folder.

$ cd projects/my-new-library/src/lib
$ ng generate module my-lib --flat --spec false

As we want others to consume this module we will need to add it to public_api.ts.

In this example, we will create a library with a component which consists of a button and a badge. Each time the button is clicked the badge will be updated and show the total number of clicks. The component should also emit an event to let the parent components know that the count has been changed and update the current count as well.

So, let’s create our component by using the “ng generate component” command as below.

ng generate component counter-button

Now we need to export the component we just created from our library along with importing the MatBadgeModule and MatButtonModule in our library module.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatBadgeModule } from '@angular/material/badge';
import { CounterButtonComponent } from './counter-button/counter-button.component';
@NgModule({
imports: [CommonModule, MatBadgeModule, MatButtonModule],
declarations: [CounterButtonComponent],
exports: [CounterButtonComponent]
})
export class MyLibModule {}

Also we will add the component to public_api.ts

export * from './lib/my-lib.module';
export * from './lib/counter-button/counter-button.component';

Now lets add logic to the component to handle the incrementing of count displayed whenever the button is clicked.

import { Component, OnInit, EventEmitter, Output } from '@angular/core';

@Component({
selector: 'lib-counter-button',
templateUrl: './counter-button.component.html',
styleUrls: ['./counter-button.component.css']
})
export class CounterButtonComponent implements OnInit {
@Output() countChanged: EventEmitter = new EventEmitter();
clickCount = 0;

constructor() {}

ngOnInit() {}

/**
* Increments the count when the button is clicked and emits an event
* to notify parent component of new count
*/
handleButtonClick() {
this.clickCount++;
this.countChanged.emit(this.clickCount);
}
}

Next, we’ll configure the component to the template.

<button type="button" id="lib-counter-button" mat-raised-button color="primary" 
[matBadge]="clickCount" matBadgeColor="accent" [matBadgeHidden]="clickCount === 0" (click)="handleButtonClick()">
Click Me!
</button>

Okay, so we have our library ready to use,  but how do we actually use it? There’s a couple of different ways.

Using your library in other applications

First is, you can use it within the application that was generated by the CLI when we first started our work. (The Angular CLI doesn’t just create a single app anymore instead it generates what the CLI team refers to as a workspace. Which means you can build multiple applications and libraries in the same directory and utilize them within other projects in the same workspace.)

If you see the tsconfig.json file in the root of your workspace. You’ll see a paths option that points to a dist/my-new-library directory.

{
"compilerOptions": {
...
"paths": {
"my-new-library": ["dist/my-new-library"]
}
}
}

This allows you to automatically use your library, after it’s been built, in other apps in the same workspace.

This is similar to using libraries installed by npm. This of course means that you must build any libraries that your application depends on, before you build the application itself, and of course will need to rebuild it every time you make a change to the library before those changes will be reflected.

A sample workflow could work like this:

$ ng build # builds your library
$ ng build # builds the application that depends on your library

Let’s build our library, then we will build an example of how to use it using the original app generated in our workspace.

$ ng build my-new-library

This generates the dist/ directory as you may know. If you open that directory and take a look , you’ll see that ng-packagr has generated bundles of the library such as FESM2015, FESM5, and UMD for consumption and generated a types file.

Now we are ready to use the library in our application.

Let’s Import the MyLibModule in src/app/app.module.ts our main module file.

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { MyLibModule } from 'my-new-library';

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, BrowserAnimationsModule, MyLibModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}

We want to display that the application is receiving the countChanged events from the library component so let’s implement handleCountChanged() in src/app/app.component.ts.

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
currentCount = 0;
handleCountChanged(e) {
this.currentCount = e; }}

Now let’s add the CounterButtonComponent to src/app/app.component.html. Also we will add a div that demonstrates the values being emitted from the component.

<div style="text-align:center; padding: 20px;">
<lib-counter-button (countChanged)="handleCountChanged($event)"></lib-counter-button>
<div>The current count is {{currentCount}}!</div>
</div>

$ ng build my-new-library # build your library

Let’s see how our example application is working. Remember to build your library before serving the application.

$ ng serve # serve the Angular app dependent on your library

Open the browser and you can see your component in action.

Using libraries like this is a great way to share code between multiple Angular applications in the same workspace. Also, if you are building something like a component library you can use the originally generated Angular application to build great working examples for your library.

Publishing your library for others to use

So, you’ve built a really cool component library and are using it in your own applications. But what if you want to share it so others can use it in their apps?

If you haven’t published anything before on npm go ahead and sign up.

$ npm adduser

After you sign in into your npm account, build your library again. This time use the –prod flag so that the Angular CLI will perform some additional steps for optimization.

$ ng build my-new-library --prod

Now let’s move into dist/my-new-library. If you want to test if your package will work in other apps or not you can link it to your local npm registry by using the following command.

$ npm link

Now time to create a new Angular workspace and link your library to the project.

$ cd ~/Desktop
$ ng new test-lib-app
$ cd test-lib-app
$ npm link my-new-library

In the new workspace add preserveSymLinks to angular.json file in the options object under the projects/test-lib-app/architect/build. This will allow the linking of your library to continue working when the app is served.

Now, use the library in the same way we did earlier and you will see that it will work here as well. If you want to remove the linked library you can use the “npm remove my-new-library” command in the project and the “npm unlink” command in the directory of your built library.

If you are ready to publish your app to npm for others run the command below inside your dist/my-new-library directory.

$ npm publish

There you go! Now, you can use your library the same way as you would use any other packages using npm install.

Read more about How to install Angular Material?

Request a quote