📜 ⬆️ ⬇️

Angular: library creation and publishing

Start over


If my memory serves me, then from version 6 in angular the opportunity to create projects of different types in one workspace: application and library.

Up to this point, people who wanted to create a library of components, most likely, used the excellent and useful package ng-packagr, which helped to create a package in the format adopted for angular. Actually, I created the previous library using this tool. Now the angular team has included ng-packagr in angular-cli and added schematics to create and build libraries, expanded the angular.json format and added some 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 

A workspace and application project will be created, let's 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 

add the key --prefix to indicate that the components and directives will use the tl prefix, that is, the component tags will be

 <tl-component-name></tl-component-name> 

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 the standard working application template angular

Creating library functionality


Let's create a library with a service, a directive, and a component. We will place the service and directive in different modules. 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 the input list of strings. 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<string> = new Subject<string>(); list: Observable<string> = 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: ` <ul> <li *ngFor="let item of list">{{item}}</li> </ul>`, 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 an export, so in the modules you must add to the exports section those components and directives that should be available.

Next, in order to make the classes from the library available in the future, add some code to 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'; 

Library connection in test application


Let's collect the library project

 ng build test-lib --watch 

Next, in the app.module, import the modules with the component and the directive and add 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 we use things from our library in the application.

 import {Component, OnInit} from '@angular/core'; import {ListService} from 'test-lib'; @Component({ selector: 'app-root', template: ` <tl-list [list]="list"></tl-list> <div tlBorder>I am bordered now</div>`, 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:



Build and publish


It remains to collect and publish the package. For building and publishing 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 make a few edits.

Rename the package to the library package.json

 /* 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 was accessible by name with the namespace, we’ll fix it a little tsconfig

 /* test-app/tsconfig.json */ *** "paths": { "@company/test-lib": [ "dist/test-lib" ], "@company/test-lib/*": [ "dist/test-lib/*" ] } *** 

And in the test application to replace imports, for example

 import {ListModule} from 'test-lib'; 

Replace with

 import {ListModule} from '@company/test-lib'; 

On it we will finish.

PS: When studying this topic, I once read the following articles
The angular library series

How to build a npm ready component library with Angular

Source: https://habr.com/ru/post/452262/


All Articles