How to Develop Angular Libraries Locally?

Altimetrik Poland Tech Blog
7 min readDec 1, 2022

--

Photo by Ryunosuke Kikuno on Unsplash

This article will show how to work with Angular libraries locally and develop them without having to constantly push them out to a remote repository.

DRY — how to avoid code repetition

If you’re working on a large and complex Angular project, I’m sure you’ll face the problem of code duplication. Let’s use the “look” of your application as an example — all pages should probably have consistent styling (after all, you don’t want to confuse your users and have different-looking buttons on every page, right?). That’s easy — you can just move those common styles to the styles.scss file, or use the 7–1 pattern to design it in an elegant manner.

But what if you have several common components that should not only look but also behave the same? That… is still easy! Just create a shared module in your application, define your components in it and export them in the module definition. Great!

Now let’s focus on a more complex problem. What if your company has more than one Angular application? And each application should look similar, for example:

  • have the same header and footer
  • have the same corporate CSS styling
  • have the same way of displaying forms, validation errors and other messages

Then you can create a shared library that can be reused by any application that needs it! Sounds great, but I bet you would still want to develop this library in the same way you develop a “normal” Angular app, with live reload etc. Guess what — I will show you how to do it. In this guideline I will be using Angular v14.

Library creation

Here you can find an official page with documentation on how to create libraries. Let’s follow the steps described there and create our library:

ng new my-shared-workspace --no-create-application
cd my-shared-workspace
ng generate library my-buttons

Voilà — library created!

  • my-shared-workspace is a common workspace for your libraries
  • my-buttons is a sample library that you can use in other projects

Now let’s make it more complicated — but only a little! Usually you will have a common prefix for your libraries, like @my-company/<library>. Let’s do that and in the my-shared-workspace/projects/my-buttons/package.jsonfile under the “name” key replace my-buttons with @my-company/my-buttons. After the replacement, thepackage.json file should look like this:

{
"name": "@my-company/my-buttons",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^14.2.0",
"@angular/core": "^14.2.0"
},
"dependencies": {
"tslib": "^2.3.0"
}
}

In the next step, let’s create a component — a fancy button that can be reused between different applications. Go to the /src/lib directory and run:

my-shared-workspace/projects/my-buttons/src/lib> ng g c fancy-button

The component has been created, so we can update its template to display the button.

<button>fancy-button works!</button>

And finally, let’s export this component in MyButtonsModule

@NgModule({
declarations: [
MyButtonsComponent,
FancyButtonComponent
],
imports: [
],
exports: [
MyButtonsComponent,
FancyButtonComponent // <-- here
]
})
export class MyButtonsModule { }

… and export it in the file my-shared-workspace/projects/my-buttons/src/public-api.ts

/*
* Public API Surface of my-buttons
*/

export * from './lib/my-buttons.service';
export * from './lib/my-buttons.component';
export * from './lib/my-buttons.module';
export * from './lib/fancy-button/fancy-button.component'; // <-- here

Creating the application

We’ve already created our library, so now it’s time to create the client application. To generate it I’ll use Angular CLI: ng new my-application, and in the application’s package.json I'll add @my-company/my-buttons as a dependency (the last dependency, after zone.js).

{
"name": "my-application",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4",
"@my-company/my-buttons": "0.0.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.6",
"@angular/cli": "~14.2.6",
"@angular/compiler-cli": "^14.2.0",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.7.2"
}
}

Next, let’s import MyButtonsModule in AppModule

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MyButtonsModule } from '@my-company/my-buttons';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
MyButtonsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

And in app.component.html I’ll display the button from the shared library.

<lib-fancy-button></lib-fancy-button>

Let’s verify that it works by running ng serve

Error: src/app/app.component.html:1:1 - error NG8001: 'lib-fancy-button' is not a known element:
1. If 'lib-fancy-button' is an Angular component, then verify that it is part of this module.
2. If 'lib-fancy-button' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.

1 <lib-fancy-button></lib-fancy-button>
~~~~~~~~~~~~~~~~~~

src/app/app.component.ts:5:16
5 templateUrl: './app.component.html',
~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component AppComponent.




** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **

Oops! The application doesn’t know where to find FancyButtonComponent!

You will also see some other errors like:

Error: src/app/app.module.ts:3:33 - error TS2307: Cannot find module '@my-company/my-buttons' or its corresponding type declarations.

3 import { MyButtonsModule } from '@my-company/my-buttons';

Running npm installalso fails because we have not deployed the library anywhere.

npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/@my-company%2fmy-buttons - Not found
npm ERR! 404
npm ERR! 404 '@my-company/my-buttons@0.0.1' is not in this registry.
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.
Npm link to the rescue

Npm link to the rescue

You can find complete documentation of the npm link command here. I’ll show you how to use this command to develop shared library locally, with live reload.

Let’s go back to the my-shared-workspace directory, build the my-buttons library (the --watch flag is responsible for the live reload), and go to the build directory.

my-shared-workspace> ng build my-buttons --configuration development --watch
Building Angular Package

------------------------------------------------------------------------------
Building entry point '@my-company/my-buttons'
------------------------------------------------------------------------------
✔ Compiling with Angular sources in Ivy full compilation mode.
✔ Writing FESM bundles
✔ Copying assets
✔ Writing package manifest
✔ Built @my-company/my-buttons

------------------------------------------------------------------------------
Built Angular Package
- from: /Users/<user>/<path>/my-shared-workspace/projects/my-buttons
- to: /Users/<user>/<path>/my-shared-workspace/dist/my-buttons
------------------------------------------------------------------------------

and now go to the directory of the built library and use this magic command:

my-shared-workspace> cd dist/my-buttons
my-shared-workspace/dist/my-buttons> npm link

added 1 package, and audited 3 packages in 1s

found 0 vulnerabilities

That’s all on the library side — now let’s move on to the client application.

In the my-application directory, in angular.json, add one more option - preserveSymlinks, which will allow us to use this linked library.

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"my-application": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/my-application",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"preserveSymlinks": true,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "my-application:build:production"
},
"development": {
"browserTarget": "my-application:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "my-application:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
}
}

And after that, in the my-application directory run the bellow command to use the linked library.

my-application> npm link @my-company/my-buttons

added 1 package, removed 1 package, and audited 918 packages in 2s

121 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

Please note that I used @my-company/my-buttons,which is the name from package.json.

After that, run ng serve again:

my-application> ng serve
✔ Browser application bundle generation complete.

Initial Chunk Files | Names | Raw Size
vendor.js | vendor | 3.30 MB |
polyfills.js | polyfills | 318.02 kB |
styles.css, styles.js | styles | 210.56 kB |
main.js | main | 12.32 kB |
runtime.js | runtime | 6.53 kB |

| Initial Total | 3.84 MB

Build at: 2022-11-24T18:27:00.803Z - Hash: 7669daf65ca2f665 - Time: 4598ms

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **

✔ Compiled successfully.

Great! Our link worked!

Let’s see the result:

Cool! Now try to change something in fancy-button-component.html

<button>fancy-button works - live reload!</button>

Client application should automatically reload.

Now you can work on both the library and the application without any inconvenience.

Verification

To check which version of the library you are using, you can run the bellow command:

npm list -g
/usr/local/lib
├── @angular/cli@14.2.6
├── @my-company/my-buttons@0.0.0-watch+1669314745555 -> ./../../../Users/<user>/<path>/my-shared-workspace/dist/my-buttons

Cleaning up

Once you have finished working on the changes locally, you can stop using the linked library in the client application as follows.

my-application> npm unlink @my-company/my-buttons --no-save

And in the library application:

my-shared-workspace/dist/my-buttons> npm rm -g @my-company/my-buttons

Now everything is cleaned up, and after running npm list -g there is no entry for @my-company/my-buttons.

Materials

Words by Aleksander Kołata, Senior Engineer at Altimetrik Poland

https://www.linkedin.com/in/aleksander-kolata/

Copywriting by Kinga Kuśnierz, Content Writer at Altimetrik Poland

https://www.linkedin.com/in/kingakusnierz/

--

--

Altimetrik Poland Tech Blog

This is a Technical Blog of Altimetrik Poland team. We focus on subjects like: Java, Data, Mobile, Blockchain and Recruitment. Waiting for your feedback!