Creating an Angular Component Library - Workspace Setup


One fairly recent feature with Angular is the ability to generate libraries with the Angular CLI. A library is essentially a group of components, directives, service, pipes, interceptors, etc that allows for maximum code reuse by installing the library in multiple applications, think DRY principles.

This next post we will setup a workspace to create a reusable component library and in subsequest post we will cover creating some components for our library.

 

Prerequisites

 

Creating Our Workspace

First we will have to create a workspace. You can think of workspaces as an root directory that may contain one or more Angular projects, projects being either applications or libraries.

Now there isn’t a command to generate a workspace per se but instead use the same new command with the CLI but with one addition. We will use the createApplication switch set to false with our new command which will prevent the CLI from generating a root application in our workspace.

ng new angular-component-library-example --createApplication=false

Now with a empty workspace created we can create a demo application and our component library. When generating applications and libraries in a workspace, the CLI will create a projects directory and place all application and libraries in this directory by default.

Lets create our demo application…

ng generate application demo --routing=true --style=scss

And our library…

ng generate library foo --prefix=foo

One thing to note is that we added a prefix switch to change our component prefixes from app to foo. This can be whatever you want it to be, I just chose something generic.

One downfall of generating libraries with the CLI is that there is not a style switch like there is with applications. By default the library will use pure CSS so if you want to use a CSS preprocessor, you will have to modify the angular.json file yourself. Under the projects attribute, find our foo library and add the schematics for scss style.

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    ...
    "foo": {
      "projectType": "library",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "root": "projects/foo",
      "sourceRoot": "projects/foo/src",
      "prefix": "foo"
    }
    ...
  }
}

We’re also going to update our package.json file with a couple scripts.

{
  ...
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build foo",
    "package": "npm run build && cd dist/foo && npm pack",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  }
  ...
}

So what did we change here? Since we’re building a library for use in other projects, we will only ever build the library and not the demo application so I modified our build script to build foo, our library. We also added a package script which does a few things. First it runs the build script, then changes directories into the build output directory, and finally runs npm pack.

This will generate our compressed tar file, dist/foo/foo-0.0.1.tgz, which can be used to install our library in other applications/projects. You would run a normal npm install <path-to-library-tar-file> to use it in another project.

Since our demo application is part of the same workspace, we don’t need to package and install our library for us to use it in demo application. If you look in our root tsconfig.json file, there is a path alias to our library’s build output directory. This allows us to reference our built library without having to actually pack and install it.

 

Creating Our Library Structure

First we are going to clean up our generated library. When generating a library with the CLI, it will auto generate a few components for us. We’re not going to need these so lets delete them.

rm projects/foo/src/lib/foo.component.*
rm projects/foo/src/lib/foo.service.*
rm projects/foo/src/lib/foo.module.*

And we will have to update our public-api.ts file by removing all the exports.

Our library is going to consist of reusable components, pipes, directives, and services. We will create dirctory for each type.

mkdir projects/foo/src/lib/components \
    projects/foo/src/lib/pipes \
    projects/foo/src/lib/directives \
    projects/foo/src/lib/services

One of roadblocks that I ran into when creating this post was figuring out how we could split our library into smaller bundles to where you wouldn’t have to pull in the entire library to use only a few components. While searching for an answer to this problem I came across a couple solutions.

One solution was to break the library up into smaller libraries so we would have to generate new libraries for each type: components, pipes, services, etc.

Another solution was to basically setup our library in a way that tells ng-packager to generate separate bundles for each of our library’s subdirectories. This will make our library tree shakable meaning that when we build an application that uses our library, the Angular compiler will shake off or exclude any bundles/modules that aren’t needed from our library. I came across this article that provides a very nice explanation and walk through of this process.

By doing this we can optimize and reduce the bundle sizes of the application using our library. While we are breaking out our library into component, service, and pipe bundles, you could go even further by breaking down each component into is own bundle. We won’t be doing that here but it is an option if you’re looking to optimize your library even further.

We will need to create a index.ts file, a package.json file, and a public_api.ts file for our components directory. The index.ts file is just a normal barrel file. The package.json file will be picked up by ng-packager to tell it to generate a new bundle for this directory. And finally, the public_api.ts will export anything we want accessible from this bundle.

touch projects/foo/src/lib/components/index.ts \
    projects/foo/src/lib/components/package.json \
    projects/foo/src/lib/components/public_api.ts

Update our index.ts file to export our public api…

export * from './public_api';

Update our package.json file to tell ng-packager our entry file…

{
  "ngPackage": {
    "lib": {
      "entryFile": "public_api.ts"
    }
  }
}

An our public_api.ts will remain empty for now but in the future it will contain exports for all the components we build.

These steps are identical for all subdirectories we want separate bundles for. For now we will just setup our components directory since we will be working mostly with creating reusable components. Later on in subsequent posts we might work with some services, directives, and possibly interceptors.

Now we can update our root library’s public_api.ts (foo/src/public_api.ts) file to export our sub directory.

export * from './lib/components';
/*
  If you setup your other directories you can export
  them here as well.
*/

Our final file structure will look something similar to this…

File tree of our library

 

Creating Shared Assets

One final step in our structure is to setup a shared assets directory for our components. We want all of our components to generally use the same color scheme, and maybe use similar animations. We will create a directory for all shared assets under our components directory of our library.

mkdir -p projects/foo/src/lib/components/_shared/scss
touch projects/foo/src/lib/components/_shared/scss/_variables.scss

Our _variables.scss file will contain all of our common shared SCSS values to keep all of our components consistently themed.

// COLORS
$default: #AAAAAC;
$primary: #DF691A;
$secondary: #3B4D63;
$success: #5cb85c;
$info: #3bb4d8;
$warning: #f0ad4e;
$danger: #d9534f;

// CONTAINERS
$border-radius: 4px;
$card-background: #FFF;

Building Our Library

Now that we have our library setup, we can easily build it with our npm script we created.

npm run build

This will build our library then output the result to our dist/ directory. A thing to remember is that every time you modify the library, you need to rebuild it in order for those changes to be available in our demo application.

If we look at the console output after our build, we will see that multiple entry points/bundles were generated.

Build bundle outputs

If you setup all of our subdirectories to generate separate bundles like we did for components, you will see that each subdirectory will have a separate entry point bundle in the output.

Next we will start creating some components to put in our library.

 

The completed github repository can be found here

 


comments powered by Disqus