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-test2. Create the library workspace
ng new superhero-library-workspaceThe
ng new workspace_namecommand 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-consumer4. Create a library in the library workspace
cd superhero-library-workspace
ng generate library superhero-libraryIn 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-libraryWhen we added our library (step #4),
@angular/clicreated the library and added the necessary configuration toangular.jsonfile which allows us to build, test, and lint our newly added library.
Press enter or click to view image in full size
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 adduserThis is a one-time operation and can be skipped if you have already logged in.
Press enter or click to view image in full size
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 publishPress enter or click to view image in full size
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-consumernpm install --save superhero-library@0.0.1
This brings the library from the public NPM repository and installs it within our consumer.
Press enter or click to view image in full size
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.jsonto useyarninstead ofnpm - It executes the
ng-addschematic 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:
Press enter or click to view image in full size
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:
Get Kashyap Mukkamala’s stories in your inbox
Join Medium for free to get updates from this writer.
— 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 oursuperhero-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-clinpm 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-projectCreate 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-librarynpm 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:
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.
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-libraryversion number - Rebuild our
superhero-library - Rebuild our schematics and place in
dist - Copy the
collections.jsonfile from projects todist
We need to rebuild our schematics every time we change and rebuild our library because the
distfolder gets overwritten and theschematicsand thecollection.jsonfile would be removed.
To help with this, we can place the below script at the root of superhero-library-workspace and invoke it viapackage.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:
Press enter or click to view image in full size
and the dist folder is now re-created as expected.
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 addsupport is a big change for us, I am going to bump the project version to 1.0.0 in thesuperhero-librarypackage 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:
Press enter or click to view image in full size
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.
Press enter or click to view image in full size
We can verify that our library-consumer has been updated as expected:
Press enter or click to view image in full size
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.