Angular: creating and publishing a library
- Tutorial
Start over
If memory serves me right, then from version 6 in angular it became possible to create different types of projects in one workspace: application and library.
Up to this point, people who wanted to create a component library most likely used the excellent and useful ng-packagr package, which helped to create the package in the format accepted for angular. Actually, I created the previous library using this tool. Now the angular team included ng-packagr in angular-cli and added schematics to create and build libraries, expanded the angular.json format and added a few more amenities. Let's now go from ng new to npm install - from creating an empty library to publishing it and importing it into a third-party project.
Workspace is created as usual
ng new test-app
Workspace and application project will be created, take a look at angular.json
{
...
"projects": {
"test-app": {
...
"sourceRoot": "src",
"projectType": "application",
"prefix": "app"
...
}
...
}
...
}
Now add the library project
ng generate library test-lib --prefix=tl
we will add the --prefix key to indicate that the components and directives will use the tl prefix, that is, the component tags will look like
Let's see now in angular.json, we have added a new project
{
...
"projects": {
"test-app": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app"
...
},
...
"test-lib": {
"root": "projects/test-lib",
"sourceRoot": "projects/test-lib/src",
"projectType": "library",
"prefix": "tl"
}
...
}
...
}
The following structure appeared in the project directory
- projects
- test-lib
ng-package.json
package.json
- src
public-api.ts
- lib
test-lib.component.ts
test-lib.module.ts
test-lib.service.ts
Also, in tsconfig.json there is an addition in the paths section
"paths": {
"test-lib": [
"dist/test-lib"
],
"test-lib/*": [
"dist/test-lib/*"
]
}
Now, if you run the application,
ng serve
then we will see a standard working angular application templateCreating library functionality
Let's create a library with a service, directive and component. We will place the service and the directive in different modules. Let's move to the projects / test-lib / src / lib directory and delete test-lib. *. Ts, also delete the contents of projects / test-lib / src / public-api.ts.
Let's move to projects / test-lib / src / lib and create modules, directive, service and component
ng g module list
ng g module border
ng g service list
/*переходим в list*/
ng g component list
/*переходим в border*/
ng g directive border
Fill the component, service and directive with logic. The component will display a list of rows submitted to the input. The directive is to add a red frame, the service will add the current timestamp to the Observable every second.
Service
/*list.service.ts*/
import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ListService {
timer: any;
private list$: Subject = new Subject();
list: Observable = this.list$.asObservable();
constructor() {
this.timer = setInterval(this.nextItem.bind(this), 1000);
}
nextItem() {
const now = new Date();
const currentTime = now.getTime().toString();
this.list$.next(currentTime);
}
}
Component List and Module
/*list.module.ts*/
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ListComponent} from './list/list.component';
@NgModule({
declarations: [
ListComponent
],
exports: [
ListComponent
],
imports: [
CommonModule
]
})
export class ListModule {
}
/*list.component.ts*/
@Component({
selector: 'tl-list',
template: `
- {{item}}
`,
styleUrls: ['./list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListComponent implements OnInit {
@Input() list: string[];
constructor() {
}
ngOnInit() {
}
}
Frame
/*border.module.ts*/
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {BorderDirective} from './border.directive';
@NgModule({
declarations: [
BorderDirective
],
exports: [
BorderDirective
],
imports: [
CommonModule
]
})
export class BorderModule {
}
/*border.directive.ts*/
import {Directive, ElementRef, OnInit} from '@angular/core';
@Directive({
selector: '[tlBorder]'
})
export class BorderDirective implements OnInit {
private element$: HTMLElement;
constructor(private elementRef$: ElementRef) {
this.element$ = elementRef$.nativeElement;
}
ngOnInit() {
this.element$.style.border = 'solid 1px red';
}
}
! Important. When generating components and libraries, cli does not create export, so be sure to add the components and directives that should be available in the exports section in the modules.
Further, so that in the future classes from the library will be available, add some code in public-api.ts
export * from './lib/list.service';
export * from './lib/border/border.module';
export * from './lib/border/border.directive';
export * from './lib/list/list.module';
export * from './lib/list/list/list.component';
Connecting a library in a test application
Let's assemble the library project
ng build test-lib --watch
Next in app.module we will import the modules with the component and the directive and add the logic
/*app.module.ts*/
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {ListModule} from 'test-lib';
import {BorderModule} from 'test-lib';
/*!!!Обратите внимание, импортим по названию пакета, а не по пути файла*/
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ListModule,
BorderModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
And use the pieces from our library in the application
import {Component, OnInit} from '@angular/core';
import {ListService} from 'test-lib';
@Component({
selector: 'app-root',
template: `
I am bordered now`,
styleUrls: ['./app.component.styl']
})
export class AppComponent implements OnInit {
list: string[] = [];
constructor(private svc$: ListService) {
}
ngOnInit() {
this.svc$.list.subscribe((value => this.list = [...this.list, value]));
}
}
Run and check the application, everything works:
Assembly and publication
It remains to collect and publish the package. For assembly and publication, it is convenient to add commands to scripts in the package.json application
{
"name": "test-app",
"version": "0.0.1",
"scripts": {
...
"lib:build": "ng build test-lib",
"lib:watch": "ng build test-lib --watch",
"lib:publish": "npm run lib:build && cd dist/test-lib && npm pack npm publish",
...
}
}
The library is compiled, published, now after installation in any other angular project
npm install test-lib
You can use components and directives.
Small note
We have a whole family of npm packages in our company, so in our case the package should be published with namespace as company / test-lib. To do this, we will only make a few edits.
Rename the package in package.json library
/* projects/test-lib/package.json */
{
"name": "@company/test-lib",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^7.2.0",
"@angular/core": "^7.2.0"
}
}
And so that in the test application the library is accessible by name with namespace, we’ll fix tsconfig a bit
/* test-app/tsconfig.json */
***
"paths": {
"@company/test-lib": [
"dist/test-lib"
],
"@company/test-lib/*": [
"dist/test-lib/*"
]
}
***
And in the test application replace the imports, for example
import {ListModule} from 'test-lib';
Replace with
import {ListModule} from '@company/test-lib';
This is the end.
PS: When I studied this topic, I once read the following articles
The Angular Library Series
How to built npm ready component library with Angular