Have you ever wondered what it takes for us, as potential library developers, to make it easy for our consumers to use our libraries? What will make them come back and ask for more? In this article, I will try and highlight some of the simple things which we can do to make our libraries a roaring success.

When we wish to use a 3rd party library in our Angular applications, we typically consider the following:

Installation Ease of Use Feature richness Upgradability Testability

#3 and #5 are the most commonly sought out qualities of a library which make them appealing.

In this 2 part article, we will be learning how to enhance the developer experience during Installation, Usage and Upgrading using @angular/cli and schematics .

Before we start, let us create a new workspace. In this workspace, we will create our library. We will also create another workspace which will consume our library.

Why do we have a workspace and a library? Starting with version 6, Angular introduced workspace which is capable of handling multiple projects with a default application created at the root

Prerequisites

To be able to follow the content of this article, It is recommend to get familiar with Angular first and install Angular CLI > v6.

Set Up

Create a folder where we are going to test all the concepts which we will explore in this article

mkdir library-test

cd library-test

2. Create the library workspace

ng new superhero-library-workspace

The ng new workspace_name command is going to prompt some questions regarding what we will be using in the default project (routing/stylesheets etc). Select anything you like.

3. Create another workspace for the consumer of the library

ng new library-consumer

4. Create a library in the library workspace

cd superhero-library-workspace

ng generate library superhero-library

In step #2 we are creating a workspace whose default web application could serve as our libraries demo website. Within this workspace, we generated a project of the type library (step #4).

This is pretty much all we need in case we want to create and publish a library to any repository (public or private).

Building, Publishing, and Consuming — the old-fashioned way

Before we optimize the usability of our library. Let us build the library that we have created, publish it to a public repository (NPM in this case) and consume it in another project.

Building

For the sake of brevity, we are going to assume that the out of the box module and component within the library are good enough for us to go to production.

We are ready to build our superhero-library code for which we can navigate to the root folder of the workspace of our library ( superhero-library-workspace ) and run the following command:

ng build superhero-library

When we added our library (step #4), @angular/cli created the library and added the necessary configuration to angular.json file which allows us to build, test, and lint our newly added library.

build library

Publishing

Since we want to publish our library to NPM, run the below command and provide NPM credentials when prompted to log in to the repository (if not done already, create a npmjs account).

npm adduser

This is a one-time operation and can be skipped if you have already logged in.

Once logged in, cd into the dist/superhero-library folder generated by the build command (from the previous step) and run the below command to publish the library to NPM.

cd dist/superhero-library

npm publish

npm publish

Consuming

The library that we have created and published is now available here. It is pretty lean and does not do much at this time except rendering the default component which was created when we generated the library.

Let us consume the superhero-library version 0.0.1 in our library-consumer which we specifically created for this purpose.

cd library-consumer npm install --save superhero-library@0.0.1

This brings the library from the public NPM repository and installs it within our consumer.

installed library

This can now be consumed in the library-consumer project by including the exported module from the superhero-library in the imports section of the main module. But that is the regular flow, the intent of this article is to help us make better libraries so that the consumers do not have to perform this and other menial tasks manually.

Next, let us take a look at how we can enhance the consumer experience at each of the touch points.

Installation

Thanks to @angular/cli we do not have to do much for making the installation easier (which is easy enough already). The ng add command is a functional wrapper around the npm install command with a few subtle differences.

It can be configured via angular.json to use yarn instead of npm It executes the ng-add schematic by default once the package is installed

This is a great resource to learn about schematics in depth. In this article (and in the ones to follow). We are going to take small steps and discuss only parts of the schematics which we wish to use.

In library-consumer workspace if we try to ng add our existing superhero-library we see the following error:

ng add schematic error

What we see above is equivalent of the npm install command that we ran earlier, except that now Angular CLI is telling us that the superhero-library is missing the schematics support (since it could not find the schematics entry point).

Schematics

The simplest and most concise description of angular schematics can be found on the NPM repository of the main schematics package.

Schematics are generators that transform an existing filesystem. They can create files, refactor existing files, or move files around. What distinguishes Schematics from other generators, such as Yeoman or Yarn Create, is that schematics are purely descriptive; no changes are applied to the actual filesystem until everything is ready to be committed. There is no side effect, by design, in Schematics.

What Schematics are:

— They are generators i.e. they are functions

— They are descriptive i.e. they only describe what they wish to achieve e.g. add a module

What Schematics are NOT:

— A way to access the filesystem and modify the files directly

Ease of Use

Being able to easily install a library ties into its overall ease of use. In this section, we will discuss how angular schematics can help us achieve the same.

Based on our terse knowledge of schematics, let us explore what schematics can do for us (and more specifically for our superhero-library ).

Before moving ahead, know that the @angular-devkit provides the schematics-cli which can be used to generate what we are about to do by hand. We are not using the CLI because the generated schematics project contains the final state of the schematic and we are trying to achieve the same step by step.

// optionally install the schematics-cli npm install -g @angular-devkit/schematics-cli

The first thing that we need to do is to create a schematics entry point for our application. When ng add is used to add a library to a workspace/project, @angular/cli parses the package.json file of the library and looks for the schematics file which consists of the various schematics that are supported by the library. If @angular/cli finds the schematics, it executes the ng-add schematic.

At this stage of our project, we wish to create a library which can automatically inject its main module into the consuming application when installed via ng add .

For that, we first create a file called collection.json which will contain all our schematics and we then define it as our entry point in the package.json file.

cd projects/superhero-project

Create the collection.json file as shown below:

This schematics file consists of only one schematic (at this time). It is named ng-add and it is the schematic which gets trigged during ng add operation (mind the — in the name of the schematic, it is needed).

Along with that, we have $schema which defines the structure of the document. This is particularly helpful since it warns us while adding incorrect properties to the collection.json file.

We now need to update the package.json file with the path to collection.json file.

This enables our project to have schematic support. We still need to implement the actual logic which gets executed when we install the library via ng-add command.

Earlier, we declared a factory when we created our ng-add schematic. This factory is executed when the ng-add schematic is invoked.

This factory has access to all the command line parameters which were passed in to the invoked schematic ( ng-add in this case and also returns a Rule which is a generator function that tells @angular what changes we want made in the consuming application.

Since schematics are declarative, we do have access to the Tree — which is the current state of the consuming applications file structure and SchematicContext — the schematic configuration and settings in context of the current execution.

In our example, we wish to create a Rule which can modify the Tree of the consuming application and insert our SuperHeroModule in the MainModule .

And since these actions are very common, there are libraries available to perform these tasks. We will be using schematics-utilities to achieve this.

Install it first:

cd projects/superhero-library npm install --save schematics-utilities

Make sure schematic specific dependencies are only added to the libraries package.json and not the workspace package.json

Angular projects use ng-packagr to build the project, so in our case, when we added schematics-utilities as a dependency we need to explicitly whitelist this dependency.

This is needed because it goes against the convention of having only peerDependencies on libraries and we are requiring our consumers to install schematic-utilities in their application as well. This whitelist can be provided in the ng-package.json file.

The utility library is not ready to be used. It provides us with handy methods for modifying the Tree and we are going to use a few of those to import our superhero-library module into the consuming applications MainModule .

We can create the factory in the location described by our schematic and with this change made, our superhero-library/schematics/ng-add/index.ts file looks as follows:

commented code indicating the Rule for ng-add

One last thing that we need to do before publishing our updated library is to compile our schematic from TypeScript to JavaScript .

For this, we can create a tsconfig.schematics.json file which can be used to compile our code and placed in the dist folder of our superhero-library .

rootDir and outDir indicating input and output for our TypeScript compiler

That’s it! We have created our schematic enabled library and we are ready to publish this to the NPM registry.

To do so, we need to perform the following steps:

Bump the superhero-library version number Rebuild our superhero-library Rebuild our schematics and place in dist Copy the collections.json file from projects to dist

We need to rebuild our schematics every time we change and rebuild our library because the dist folder gets overwritten and the schematics and the collection.json file would be removed.

To help with this, we can place the below script at the root of superhero-library-workspace and invoke it via package.json scripts .

and this can be invoked from the scripts section of the superhero-library-workspace package.json file as shown below:

{

... "scripts": { "package": "sh pack.sh", }, ...

}

and when executed, we see the following:

build library, schematics and copy collections

and the dist folder is now re-created as expected.

generated distribution folder

We are now ready to publish this bundle to NPM and consume it in our library-consumer workspace.

Remember to bump the major/minor/patch version of the library in the libraries package.json file

To publish, we can extend our pack.sh file and add commands to publish on our behalf (assuming that npm adduser is already run successfully).

Since ng add support is a big change for us, I am going to bump the project version to 1.0.0 in the superhero-library package before executing the package command.

We can now run npm run package command from the root of the superhero-library-workspace and our new version gets published:

package published with updated version

The package is successfully pushed and available here. Now, to consume this, we can use the ng add command to add the superhero-library in our library-consumer .

superhero-library 1.0.0 installed via ng add

We can verify that our library-consumer has been updated as expected:

Updated Module based on the ng-add schematic

Conclusion

Now that we have the basic schematic working and our project is ng add enabled. We are ready to move on and learn more complex operations which can be performed using schematics which can in turn be used to enhance other developer experiences listed in this article.

The entire code base used in this article is available here.

Part 2 coming soon.