A Guide for Building Angular 6 Libraries

In an earlier post, I wrote about Angular CLI Workspaces, a new feature in Angular 6 that allows you to have more than one project in a single workspace (Angular CLI environment). In this post, I will show you how to create angular 6 Libraries. On top of that, I will show you how to pass configurations and data to your new library from your Angular Application.

Why?

If you have multiple active angular projects, you may have come to realize that there so many things they share or have in common. Sometime, as a developer you are forced to copy paste their code to new projects for features they have in common.

A good example of this is Authentication. While the UI (Sign in, sign up, password reset etc. forms in the case of Authentication) may change, the underlying code (Services, HTTP Interceptors, Guards etc.) tends to remain the same apart from some incremental modifications and bug fixes.

Wouldn’t it be easier that instead of copy pasting, you could just have a private library that you can hook into your project and have access to all the features you need for your new project? This would save you lots of hours which you could utilize on the UI and other features of your new project.

Overview of our Library

We are going to work with a demo that does the following:

A component to display a list of Football/Soccer National Teams and the number of World Cup trophies they have won. A service that will take a list of teams Injected to our library and sort it by descending order. The config file will be injected using the forRoot method through the module (Similar to the way you pass routes to the router module).

imports: [ RouterModule.forRoot(routes) ]

NB: To inject our configuration, we will rely on Injection Token method. It is not the only way to pass configurations into a library or module. If you want to explore the other methods, I suggest going over this excellent post by Michele Stieven.

For our demo, we are going to rely on the following libraries: Angular Material, Angular CDK and Angular Flex Layout.

Getting Started

To get started, we are going to create a new angular project (just a regular project):

ng new application-name

Then, open that directory on terminal or PowerShell:

cd application-name

And generate a library inside our angular workspace using ng generate library command.

ng generate library test-library --prefix=tl

A new library is generated and is placed under the /projects/my-library directory in your angular workspace. To use your library in your current app, you need to build your library every time. To build the library, use the ng build command followed by the name of the project (library in this case):

ng build my-library
ng build --prod my-library

The build artefacts can be found under a directory with the name of the library, inside the /dist directory in your workspace.

Working on the library

If you open your project folder, the folder structure will look like this:

A Guide to Building Angular 6 Libraries

When you generate a library, Angular CLI will creates 3 files: the main library module, a component and a service. For our demo, there is no need to add any extra component or service. But, if you need to add one, the ng generate command with the project flag will do the trick.

ng generate component component-name --project-name=test-library
ng generate service service-name --project-name=test-library

Building the Library

Our demo library can be broken down into 3 items: The Library Module (think of AppModule), Our Service and Component. On top of the three items, there are classes and interfaces that will play a supporting role for our library. You can get the whole source code on Github here.

First, we need to define an injection token constant to use Injection Token method:

export const TestLibConfigService = new InjectionToken<TestLibConfig>('TestLibConfig');

And then, we need to define our interface(TestLibConfig) for the object we will be passing using our forRoot method.

export interface TestLibConfig {
  title: string;
  teams: {
    country: string;
    trophies: number;
  }[];
  sport: string;
}

NB: To avoid Circular dependency warning, you can put the TestLibConfig and TestLibConfigService into separate files instead of the module file. These warnings occur because the two will be imported by the service and the module, while the module will import the service.

Library Module

Since we now have an injection token, we can now go ahead and create our forRoot method that we will use to pass the configuration object to our library.

static forRoot(config: TestLibConfig): ModuleWithProviders {
  return {
    ngModule: TestLibraryModule,
    providers: [
      TestLibraryService,
      {
         provide: TestLibConfigService,
         useValue: config
      }
    ]
  };
}

As you can see, our forRoot method takes a config object and provides it together with the services that are in our library.

Also, we need to import Material Components we are using in our Library and declare and export our component.

@NgModule({
  imports: [BrowserModule, FlexLayoutModule, MatListModule, MatToolbarModule, MatCardModule],
  declarations: [TestLibraryComponent],
  exports: [TestLibraryComponent]
})
Library Service

Then, we need to inject the configuration object into our service.

constructor(@Inject(TestLibConfigService) private config: TestLibConfig) {
  console.log(config);
}

Now the configuration object is ready for use in our service. The rest of the service looks like this:

export class TestLibraryService {
// inject our configuration into our service
constructor(@Inject(TestLibConfigService) private config: TestLibConfig) {
  console.log(config);
}

// You can now use your configuration as you normally would
// if it was a http request, then you could return observables which you can subscribe to
getConfig(): Observable<TestLibConfig> {
  return of(this.config);
}

getTitle(): Observable<string> {
  return of(this.config.title);
}

getTeams(): Observable<{ country: string; trophies: number }[]> {
    // just for fun, let's sort this by descending order
    // we shall use, bubble sort
    const sorted = this.config.teams.slice();
    for (let i = 0; i < sorted.length; i++) {
      for (let j = 0; j < sorted.length - 1; j++) {
        if (sorted[j].trophies < sorted[j + 1].trophies) {
          const swap = sorted[j];
          sorted[j] = sorted[j + 1];
          sorted[j + 1] = swap;
        }
      }
    }
    return of(sorted);
  }
  
  getSport(): Observable<string> {
    return of(this.config.sport);
  }
}
Our Component:

And finally, our service is ready to be used by our library component normally. Here is the component content:

export class TestLibraryComponent implements OnInit {
  public teams$: Observable<{ country: string; trophies: number }[]>;
  public title$: Observable<string>;

  constructor(private testLibService: TestLibraryService) {}

  ngOnInit() {
    this.teams$ = this.testLibService.getTeams();
    this.title$ = this.testLibService.getTitle();
  }
}

And the template looks like this (Remember I am using Angular Material, you can replace everything easily with any UI Library like Bootstrap and it will still work):

<mat-toolbar color="primary">
  <span>Demo App</span>
</mat-toolbar>
<div gdAuto>
  <div fxLayout="column" fxFlex.xs="100" fxFlex="600px" fxFlexOffset="calc(50% - 300px)" fxFlexOffset.xs="0">
    <mat-card>
      <mat-card-header>{{ title$ | async }}</mat-card-header>
      <mat-card-content>
        <mat-list role="list">
          <mat-list-item *ngFor="let x of teams$ | async; let i = index" role="listitem"> {{ i + 1 }}. {{ x.country }} - {{ x.trophies }}</mat-list-item>
        </mat-list>
      </mat-card-content>
    </mat-card>
  </div>
</div>

Next, build the library:

ng build –prod test-library

Using our Library in our Angular App

After building the library is complete, go ahead and import it to your Angular application:

import { TestLibraryModule, TestLibConfig } from 'test-library';

And add it to your imports array:

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    TestLibraryModule,
    TestLibraryModule.forRoot(testLibConfigs)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Don’t forget to define the testLibConfigs constant which we are passing to the library:

const testLibConfigs: TestLibConfig = {
  title: 'World Cup Teams',
  teams: [
    {
      country: 'France',
      trophies: 2
    },
    {
      country: 'Brazil',
      trophies: 5
    },
    {
      country: 'Germany',
      trophies: 4
    },
    {
      country: 'Argentina',
      trophies: 2
    },
    {
      country: 'Spain',
      trophies: 1
    },
    {
      country: 'England',
      trophies: 1
    },
    {
      country: 'Uruguay',
      trophies: 2
    },
    {
      country: 'Italy',
      trophies: 4
    }
  ],
  sport: 'Football/Soccer'
};

And finally, you can now use the library component inside your app components as shown below:

<div gdAuto>
  <tl-test-library></tl-test-library>
</div>

NB: Our demo library is using the prefix tl. By default, libraries will use app prefix just like a normal angular app. You can specify the prefix for the library when generating a library using –prefix flag.

Deploying your Library

When you have build your library, you will want to deploy it. To do that, just navigate to the build artefacts of your library and run NPM publish.

cd dist/test-library
npm publish

8 Replies to “A Guide for Building Angular 6 Libraries”

  1. Hi,

    Thanks for the article. I tried to implement the same and it’s working as expected.

    But after creating library build, my angular application is not refreshing automatically. Getting an error as mentioned below.

    ERROR in D:/ROV-MobileApp/Projects/RnD/Angular 6/my-workspace/my-workspace-project/dist/test-library/fesm5/test-library.js
    Module build failed: Error: ENOENT: no such file or directory, open ‘D:\ROV-MobileApp\Projects\RnD\Angular 6\my-workspace\my-workspace-project\dist\test-library\fesm5\test-library.js’

    I have to kill the “ng serve” process and re-run the “ng serve” command to see the changes into application.

    is it the default expected behavior or i am doing some mistakes ? Please help.

    1. Yes, that is the expected behavior. When you build your library, angular cli will first delete the old version from dist folder which courses the error. You can try and use --delete-output-path false flag and see whether angular cli will pick the changes automatically.

      1. Do you mean to say that while making build for library i need to use that flag i.e.

        ng build –prod test-library –delete-output-path false

        I have tried it, but it’s giving me an error i.e. Unknown option: ‘–deleteOutputPath’

        1. Just checked, it seems that libraries do not support that option. I have been building libraries in Angular and everytime I make changes, I have had to restart ng serve for the new changes to take effect.

          1. Ohhh, that’s really painful while doing development. Apart from that, it’s really a great way to save our efforts and make things reusable across multiple angular application.

            Many thanks for the explanation 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.