/ AOT

Writing an AOT Compliant Angular Library

As the use of Angular expands in my workplace, I find myself delving deeper into the corners of the Angular framework. Recently, this happened to be library development. To my surprise, there seemed to be a lack of relevant material on the subject, which led to a lot of digging and experimentation. The following write up details my experience, which I hope will prove useful to others as they attempt to tackle library development.

Background

Let's take a step back. Why did I find the need to create my own libraries, and why must it be AOT compliant? What is "AOT compliance", and why should anyone care?

Standardize with libraries

In our workplace, we have 4 developers working in the Angular realm, with varying levels of experience. Up until this point, shared services and components have been passed around from person to person in crude file sharing methods. As changes are made to different versions of the library, we end up with vastly different variations of what was originally the same library. This also makes it difficult to find the "latest" version of the library.

To solve this, I created a private, IP secure NPM repository using Verdaccio. This repository would serve as the home of our company's libraries. As long as all developers agree to use this NPM repository as the source of truth, we can successfully control what changes to the library are published and used in production applications. This has the added benefit of making it very simple to install and use these libraries through NPM.

AOT Compliance

AOT stands for Ahead Of Time compilation. Without going into too much detail, AOT compilation has the following benefits:

  1. Less code generated in the production bundle. A compiler is not included in the output, and tree-shaking reduces unneeded dependencies.

  2. Faster rendering. This means a faster application for the end user.

  3. Compile time syntax checking. Various template errors will be caught at compile time instead of run time.

  4. Improved Security. Since templates are compiled ahead of time, there is less room for HTML injection.

Due to all of these benefits, the majority of our newer applications utilize AOT compilation. When an application compiles ahead of time, all libraries used by the app must also be AOT compliant. Thus, this became a requirement for any library I was going to make.

Library Development

Now, with a better understanding of the topic, we will transition into the instructional portion of this post. I will walk you step by step through creating a library that will successfully compile with an AOT compliant application. Let's get started.

Example Code

The instructions to follow have been replicated and uploaded to Github repositories for those that learn better by having a complete example (myself included). Refer to the commit history if you'd like to follow along.

Angular Library Example:
https://github.com/dylanb124/angular2-library-example

Angular Client App Example:
https://github.com/dylanb124/angular2-client-app-example

Scaffolding with Yeoman

To avoid having to self-maintain my own library template, I use the Yeoman Angular Library Generator.

[Yeoman generator for Angular library
Follow the excellent instructions provided on the github page for the generator in order to get it installed and generate a source controlled library. While this gets us 90% of the way there, I have made some of my own modifications to the generated library in support of AOT compilation, which I will detail below.

File Cleanup

Depending on the type of library you are making, you will most likely want to delete some of the example files. In this tutorial, I am going to be creating a service library. I will be keeping the sample.service.ts file, and deleting sample.component.ts, sample.directive.ts, and sample.pipe.ts.

Once you have removed the example files you won't be using, you will have to remove the imports from index.ts.

import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
// import { SampleComponent } from './src/sample.component';
// import { SampleDirective } from './src/sample.directive';
// import { SamplePipe } from './src/sample.pipe';
import { SampleService } from './src/sample.service';

// export * from './src/sample.component';
// export * from './src/sample.directive';
// export * from './src/sample.pipe';
export * from './src/sample.service';

@NgModule({
  imports: [
    CommonModule
  ],
  // declarations: [
  //  SampleComponent,
  //  SampleDirective,
  //  SamplePipe
  // ],
  // exports: [
  //  SampleComponent,
  //  SampleDirective,
  //  SamplePipe
  // ]
})
export class SampleModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: SampleModule,
      providers: [SampleService]
    };
  }
}

Configuration Changes

Since we are making the library AOT compliant, there are a few changes we can implement to make development go smoother.

First, add the following to the bottom of tsconfig.json:

"angularCompilerOptions": {
  "strictMetadataEmit": true,
  "genDir": "compiled"
}

This will output AOT specific files to a compiled folder, which you will want to create in the project root directory. The files generated in this folder should end with a ngsummary.json extension.

However, there's no need to track these files in our Git repo, so we will want to add the following to .gitignore.

# Ignore AOT generated files
*.ngsummary.json

In addition to this, we will want to add the same to .npmignore to ensure summary files are not published to the NPM repository either. While you are here, you may find it useful to exclude lint files as well.

# Ignore AOT generated files
*.ngsummary.json

# Ignore linting
tslint.json

Feel free to make changes to package.json and README.MD at this point to reflect application specific information. However, this is entirely optional.

Test Updates

At this point, you'll want to make sure the changes you have made are working as expected.

  1. Build the application with the typescript compiler. Run the following command in the command line: npm run tsc. You should now have new files in the dist folder.

  2. Build the application with the AOT compiler. Run the following command in the command line: npm run build. You should now have new files in the compiled folder.

If you had any compilation errors, you will want to review the instructions up to this point and ensure you have followed the steps properly. If you are unable to resolve your issue, please feel free to leave a comment and I will do my best to assist you.

Service Parameters

This section will prove useful to libraries that provide a service and require arguments from the calling application.

Library

To start with, we will have to expose a parameter for the parent application to use. If we want our library to be AOT compliant, we will need to expose this as a function in index.ts:

export class SampleModule {
  static forRoot(getConfig: Function): ModuleWithProviders {
    return {
      ngModule: SampleModule,
      providers: [
        { provide: 'config', useFactory: getConfig },
        SampleService
      ]
    };
  }
}

This config value may now be accessed through dependency injection anywhere in the library. For example, in sample.service.ts:

import { Inject, Injectable } from '@angular/core';

@Injectable()
export class SampleService {

  constructor(@Inject('config') private config: string) {
    console.log(this.config);
  }
}

In real usage, the config variable would be typed as a custom interface or class containing the configurable properties. However, I am using a string for the sake of simplicity.

Client Application

Now that we have exposed the parameters, we need to figure out how to pass configuration values from the client application. For my example, I will be generating an app using the popular CLI tool, Angular CLI.

First, we need to package up our library so we can use it in other applications. I would suggest locally packaging the file, as opposed to publishing it to an NPM repository.

npm pack

This should generate a file called [LIBRARY_NAME]-[LIBRARY_VERSION].tgz. I suggest moving this file to the project directory of your client application.

Now that we have the library packaged up, we can install it from the project directory of our client application using the following command:

npm install [LIBRARY_NAME]-[LIBRARY_VERSION].tgz

Once installed, the library can be imported into app.module.ts, the same place where we will pass our configuration arguments to the library service.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { SampleModule } from 'angular2-library-example';

export function createConfig(): string {
  return 'Config injection successful!';
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    SampleModule.forRoot(createConfig)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We can then inject the service in any component we like, such as app.component.ts.

import { Component } from '@angular/core';
import { SampleService } from 'angular2-library-example';

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

  // Inject sample serivce
  constructor(private sampleService: SampleService) {

  }
}

If all works as planned, you should be able to AOT build and run the application, receiving the following message in the browser console:

Config injection successful!

Note: If you are using Angular CLI, you may build and run your application by issuing the ng serve --aot command from your project root directory.

Publish Your Library

There's a high probability that you'll want to eventually publish this library to either the public NPM repository or to a private NPM repository. Luckily for you, NPM makes this very easy to do.

Public

Ensure your package.json file of your library is configured appropriately, and run the following in the command line:

npm publish
Private

Append to the bottom of your package.json file the private repository which you wish to publish to:

"publishConfig": {
  "registry": "http://MY-REPO-URL/"
}

And then perform the following in the command line:

npm config set registry <registry url>
npm login
npm publish

Summary

After reading this blog post, you should be able to accomplish the following:

  1. Create an Angular library using a template generator
  2. Implement an AOT compatible service in the library that accepts arguments
  3. Successfully install and use your library in any Angular application
  4. Publish the library to an NPM repository

Thank you for reading. If you'd like to leave feedback or have any questions, please feel free to leave a comment and I'll get back to you promptly.