0%

A question from stackoverflow

The following code does not work, do you know why? When you run the code, you will got the error TypeError: Cannot read properties of undefined (reading 'navigate').

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import {Component, OnInit} from '@angular/core';
import {Router, RouterOutlet} from '@angular/router';
import {TestComponent} from "./test/test.component";

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, TestComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit {
title = 'angular-18';

constructor(private router: Router) {
}

gotoProduct() {
this.router.navigate(['product']);
}

ngOnInit() {
setTimeout(this.gotoProduct, 1000);
}
}

Reason:
Code executed by setTimeout() is called from an execution context separate from the function from which setTimeout was called. The usual rules for setting the this keyword for the called function apply, and if you have not set this in the call or with bind, it will default to the window (or global) object, even in strict mode. It will not be the same as the this value for the function that called setTimeout. - From MDN- The this problem for setTimeout

That means router is undefined in gotoProduct function. The reason is that this in gotoProduct function is not the instance of AppComponent. It is the instance of Window object.

How to fix it?

Use Arrow function

Arrow function doesn’t have its own this binding, it will inherit the this from the parent scope. So, you can use arrow function in setTimeout function to keep the context of the parent scope.

1
2
3
setTimeout(() => {
this.gotoProduct();
}, 1000);

Use bind

You can also use bind function to bind the this to the gotoProduct function.

1
setTimeout(this.gotoProduct.bind(this), 1000);

Conclusion

Becareful when using setTimeout function in Angular.
Always use arrow function in setTimeout function to keep the context of the parent scope.

Introduction

HostListener is an Angular Decorator that declares a DOM event to listen for, and provides a handler method to run when that event occurs.

What is a Host?

先看一下Host的含义,Host是指Directive或者Component的宿主元素,假设有一个ProductComponent组件,它的模板如下:

1
2
3
4
5
6
7
8
9
10
import {Component} from '@angular/core';

@Component({
selector: 'app-product',
template: `
<div>Product</div>
`,
})
export class ProductComponent {
}

我们在app.component.html中使用ProductComponent组件,代码如下:在这里,app-product就是ProductComponent的Host。

1
<app-product></app-product>

Render后的DOM结构如下:

1
2
3
<app-product>
<div>Product</div>
</app-product>

也可以这样理解,一个组件(指令)的Host就是在使用组件(指令)时,它们的选择器对应的元素。

See here for details.

Here is an example of using HostListener to listen for the click event on a button element:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {Component, HostListener} from '@angular/core';

@Component({
selector: 'app-test',
standalone: true,
imports: [],
template: `
<button>Click me</button>
`,
})
export class TestComponent {
@HostListener('click')
onClick() {
console.log('Button clicked');
}
}

The above code is equivalent to the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {Component, HostListener} from '@angular/core';

@Component({
selector: 'app-test',
standalone: true,
imports: [],
template: `
<button (click)="onClick()">Click me</button>
`,
})
export class TestComponent {
onClick() {
console.log('Button clicked');
}
}

HostListener is a more powerful way to listen for events because it allows you to listen for events on any element, not just the element that the HostListener is attached to. For example, you can listen for the events from document or window object.

  • document:click
  • window:keydown
  • window:scroll
  • window:resize
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import {Component, HostListener} from '@angular/core';

@Component({
selector: 'app-test',
standalone: true,
imports: [],
template: `
<button (click)="onClick()">Click me</button>
`,
})
export class TestComponent {
onClick() {
console.log('Button clicked');
}

@HostListener('document:click', ['$event'])
documentClick(event: MouseEvent) {
console.log(`Document clicked at (${event.clientX}, ${event.clientY})`);
}

@HostListener('window:keydown', ['$event'])
keydown(event: KeyboardEvent) {
console.log(`event.key: ${event.key} was pressed`);
}

@HostListener('window:scroll', ['$event'])
onScroll(event: Event) {
console.log(`Window scrolled`);
}

@HostListener('window:resize', ['$event'])
onResize(event: Event) {
console.log(`Window resized`);
}
}

References

https://angular.dev/api/core/HostListener?tab=usage-notes

Introduction

In this article, we will explore the Angular Module Federation feature in depth. Please read the first article in this series to get a basic understanding of Module Federation.

remoteEntry.js

Each time you call loadRemoteModule, Angular will fetch the remoteEntry.js file first from the remote server. What is this file? Let’s take a look at the remoteEntry.js file in the remote project.

You can find the remoteEntry.js file in the dist folder of the remote project. The file is generated by the ModuleFederationPlugin in the webpack configuration. But the file under the dist folder is minimized and hard to read. You can read it on Chrome network tab when you call loadRemoteModule.

This file is very large and contains lots of code, you can scroll down to the bottom or search container entry to find key part.

remoteEntry.js mainly contains the following information:

1. The list of modules that are exposed by the remote project.

In the following example, the remote project exposes a module named ./Component from its ./projects/mfe1/src/app/product/product.component.ts file.

1
2
3
4
5
var moduleMap = {
"./Component": () => {
return __webpack_require__.e("projects_mfe1_src_app_product_product_component_ts").then(() => (() => ((__webpack_require__(/*! ./projects/mfe1/src/app/product/product.component.ts */ 1585)))));
}
};

2. The list of shared libraries.

From the following code in remoteEntry.js the remote project shares the common angular and rxjs libraries.

1
2
3
4
5
6
7
8
9
10
11
12
13
switch(name) {
case "default": {
register("@angular/common/http", "17.3.0", () => (__webpack_require__.e("node_modules_angular_common_fesm2022_http_mjs-_f9340").then(() => (() => (__webpack_require__(6443))))));
register("@angular/common", "17.3.0", () => (__webpack_require__.e("node_modules_angular_common_fesm2022_common_mjs-_b4621").then(() => (() => (__webpack_require__(316))))));
register("@angular/core/primitives/signals", "17.3.0", () => (__webpack_require__.e("node_modules_angular_core_fesm2022_primitives_signals_mjs").then(() => (() => (__webpack_require__(5689))))));
register("@angular/core", "17.3.0", () => (__webpack_require__.e("node_modules_angular_core_fesm2022_core_mjs").then(() => (() => (__webpack_require__(7580))))));
register("@angular/platform-browser", "17.3.0", () => (__webpack_require__.e("node_modules_angular_platform-browser_fesm2022_platform-browser_mjs-_e89f0").then(() => (() => (__webpack_require__(436))))));
register("@angular/router", "17.3.0", () => (__webpack_require__.e("node_modules_angular_router_fesm2022_router_mjs-_5a221").then(() => (() => (__webpack_require__(5072))))));
register("rxjs/operators", "7.8.0", () => (__webpack_require__.e("node_modules_rxjs_dist_esm_operators_index_js").then(() => (() => (__webpack_require__(8219))))));
register("rxjs", "7.8.0", () => (__webpack_require__.e("node_modules_rxjs_dist_esm_index_js").then(() => (() => (__webpack_require__(845))))));
}
break;
}

Conclusion

  1. remoteEntry.js is a file that contains the list of modules that are exposed by the remote project.
  2. Angular fetches the remoteEntry.js file first before loading the remote module.
  3. The remoteEntry.js file is generated by the ModuleFederationPlugin in the webpack configuration.
  4. shell/host project does not have a remoteEntry.js file.

loadRemoteModule

How loadRemoteModule works in Angular Module Federation? Let’s take a look at the source code of the loadRemoteModule function in the @angular-architects/module-federation package.

The entry point is from the router file, we call loadRemoteModule function to load the remote module. The loadRemoteModule function is defined in the load-remote-module.ts file in the @angular-architects/module-federation package.

1
2
3
4
5
6
7
8
9
{
path: 'product',
loadComponent: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './Component'
}).then(m => m.ProductComponent)
}

Here is the source code of the loadRemoteModule function from webpack:///node_modules/@angular-architects/module-federation-runtime/fesm2022/angular-architects-module-federation-runtime.mjs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
async function loadRemoteModule(optionsOrRemoteName, exposedModule) {
let loadRemoteEntryOptions;
let key;
let remoteEntry;
let options;
if (typeof optionsOrRemoteName === 'string') {
options = {
type: 'manifest',
remoteName: optionsOrRemoteName,
exposedModule: exposedModule,
};
}
else {
options = optionsOrRemoteName;
}
// To support legacy API (< ng 13)
if (!options.type) {
const hasManifest = Object.keys(config).length > 0;
options.type = hasManifest ? 'manifest' : 'script';
}
if (options.type === 'manifest') {
const manifestEntry = config[options.remoteName];
if (!manifestEntry) {
throw new Error('Manifest does not contain ' + options.remoteName);
}
options = {
type: manifestEntry.type,
exposedModule: options.exposedModule,
remoteEntry: manifestEntry.remoteEntry,
remoteName: manifestEntry.type === 'script' ? options.remoteName : undefined,
};
remoteEntry = manifestEntry.remoteEntry;
}
else {
remoteEntry = options.remoteEntry;
}
if (options.type === 'script') {
loadRemoteEntryOptions = {
type: 'script',
remoteEntry: options.remoteEntry,
remoteName: options.remoteName,
};
key = options.remoteName;
}
else if (options.type === 'module') {
loadRemoteEntryOptions = {
type: 'module',
remoteEntry: options.remoteEntry,
};
key = options.remoteEntry;
}
if (remoteEntry) {
await loadRemoteEntry(loadRemoteEntryOptions);
}
return await lookupExposedModule(key, options.exposedModule);
}

The key part is the last if branch in this file.
first: it load the remoteEntry.js file by calling loadRemoteEntry function.

1
2
3
if (remoteEntry) {
await loadRemoteEntry(loadRemoteEntryOptions);
}

loadREmoteEntry function then calls loadRemoteModuleEntry function to load the remote module.

1
2
3
4
5
6
7
8
9
10
// remoteEntry = 'http://localhost:4201/remoteEntry.js'
async function loadRemoteModuleEntry(remoteEntry) {
if (containerMap[remoteEntry]) {
return Promise.resolve();
}
return await import(/* webpackIgnore:true */ remoteEntry).then((container) => {
initRemote(container, remoteEntry);
containerMap[remoteEntry] = container;
});
}

We can see that it uses the import function to load the remoteEntry.js file. The import function is a dynamic import function that fetches the remoteEntry.js file from the remote server. After loading the remoteEntry.js file, it calls the initRemote function to initialize the remote container. This container is used to get the remote module later.

After loading the remoteEntry.js file, then it calls lookupExposedModule function to get the module from the remote project.

1
2
3
4
5
6
7
8
// key = 'http://localhost:4201/remoteEntry.js'
// exposedModule = './Component'
async function lookupExposedModule(key, exposedModule) {
const container = containerMap[key];
const factory = await container.get(exposedModule);
const Module = factory();
return Module;
}

Constructor vs ngOnInit

What’s the difference between constructor and ngOnInit in Angular?
constructor is a TypeScript class constructor while ngOnInit is an Angular lifecycle hook called by Angular to indicate that Angular is done creating the component. a common question is when to use constructor and when to use ngOnInit?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {Component, OnInit} from '@angular/core';

@Component({
selector: 'app-product',
standalone: true,
imports: [],
templateUrl: './product.component.html',
styleUrl: './product.component.scss'
})
export class ProductComponent implements OnInit {
constructor() {
// What should be done here?
}

ngOnInit() {
// What should be done here?
}
}
  • constructor is used to initialize the class properties, but it’s not the right place to do any initialization that depends on Angular services or inputs. ngOnInit is the right place to do such initialization.
  • ngOnInit is an Angular lifecycle hook that is called after Angular has initialized all data-bound properties of a directive. It’s the right place to put initialization logic that depends on Angular services or inputs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {Component, OnInit} from '@angular/core';
import {DataService} from "../data.service";

@Component({
selector: 'app-product',
standalone: true,
imports: [],
templateUrl: './product.component.html',
styleUrl: './product.component.scss'
})
export class ProductComponent implements OnInit {
private products: any[] = [];

// Dependency Injection goes here.
constructor(private dataService: DataService) {
// What should be done here?
}

ngOnInit() {
this.dataService.fetchData().subscribe((data: any) => {
this.products = data;
});
}
}

Conclusion

When to call Call count What to do Comments
constructor When create class 1 1. Initialize class properties, 2. DI
ngOnInit after constructor and the first ngOnChanges 1 1. Init @Input, 2. Call service to fetch data

References

Introduction

How Angular handles assets file such as images, fonts and other static files in the project.

Angular 18+

The following config from angular.json indicates that the Angular builder will copy everything under public folder to the outputPath: dist folder.

1
2
3
4
5
6
"assets": [
{
"glob": "**/*",
"input": "public"
}
],

So

  1. Put your logo file(angular.svg) under public folder
  2. In your component file, you can just use it as below:
1
<img src="angular.svg" alt="Angular Logo" />

To verify whether the image was in the final build, you can run the following command and check the dist folder under project root.

1
ng build

If you want to use the old way, you can config angular.json file as below, in this config, we copy everything under src/assets folder to dist folder.(Note, if you omit the output option, the assets folder will not copied, it only copy images folder)

1
2
3
4
5
6
7
8
9
10
11
"assets": [
{
"glob": "**/*",
"input": "public"
},
{
"glob": "**/*",
"input": "src/assets",
"output": "/assets"
}
],

Then use it in your component file as below:

1
<img src="assets/images/angular.svg" alt="Angular Logo" />

Before Angular 18

angular.json config

1
2
3
4
"assets": [
"src/favicon.ico",
"src/assets"
],

Asset file path: src/assets/images/angular.svg

Use it in your component file as below:

1
<img src="assets/images/angular.svg" alt="Angular Logo" />

References

  1. https://angular.dev/reference/configs/workspace-config#assets-configuration

Introduction

When you are developing an Angular application, you may need to call some APIs from the backend server. But in the development environment, the backend server may not be ready yet, or you want to use a local backend server for testing. In this case, you can use the proxy configuration to redirect the API requests to another server.

1. Create proxy file

Create a new file named proxy.conf.json in the root src folder of your Angular project. The content of the file should look like this:

1
2
3
4
5
6
{
"/api": {
"target": "http://localhost:3000",
"secure": false
}
}

In this example, we are redirecting all requests that start with /api to http://localhost:3000. You can change the target URL to your backend server address.

2. Update angular.json

Open the angular.json file in the root of your Angular project. Find the serve section under projects > your-project-name > architect > serve > configuration > development. Add the proxyConfig option with the path to the proxy.conf.json file.

1
2
3
4
5
6
7
8
9
10
11
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "angular-16:build:production"
},
"development": {
"proxyConfig": "src/proxy.conf.json", // <--- Add this line
"browserTarget": "angular-16:build:development"
}
},

Note: you can also serve the proxy file in ng serve command as below.

1
ng serve --proxy-config src/proxy.conf.json

3. Start the development server

Now you can start the development server with the following command:

1
ng serve

4. Example of backend server(Express)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express');
const app = express();
const port = 3000;

// Sample user information
const userInfo = {
id: 1,
name: 'John Doe',
email: 'john.doe@example.com'
};

// Define the /api/user route
app.get('/api/user', (req, res) => {
res.json(userInfo);
});

// Start the server
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});

5. Troubleshooting

  1. Make sure your backend server is running on the specified target URL. you can test your api from the browser or using tools like Postman to make sure it works.
  2. Don’t call backend api directly, only calls made to dev server will be proxied.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fetchUser() {
    return this.http.get<any>('/api/user'); // OK
    }

    fetchUser() {
    return this.http.get<any>('http://localhost:4200/api/user'); // OK
    }

    fetchUser() {
    return this.http.get<any>('http://localhost:3000/api/user'); // Won't work!
    }
  3. Make sure the path of proxy config file is correct in angular.json file.

The npm install --production command is used to install only the dependencies listed under the dependencies section in the package.json file, excluding the devDependencies. This is typically used in production environments where you want to avoid installing unnecessary development tools and libraries.

Here is a brief explanation of when and why you might use npm install --production:

  1. Production Deployment: When deploying an application to a production environment, you often want to minimize the size of the deployment package and reduce the number of installed packages to only those necessary for running the application. This helps in improving performance and security.

  2. Server Environments: In server environments where the application is running, you generally do not need development tools like testing frameworks, linters, or build tools. Using npm install --production ensures that only the essential packages are installed.

  3. Docker Images: When building Docker images for your application, using npm install --production can help create smaller and more efficient images by excluding development dependencies.

Example usage:

1
npm install --production

This command will read the package.json file and install only the packages listed under dependencies, ignoring those under devDependencies.

Introduction

In RxJS, there is a timeout operator, it’s used to throw an error if the source observable does not emit a value within a specified timeout duration.

Use case

In Angular, the HttpClient service is used to make HTTP requests. Sometimes, we want to set a timeout for the request, if the request does not complete within the specified time, we want to cancel the request and show an error message to the user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { catchError, timeout } from 'rxjs/operators';
import { throwError } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class ApiService {
constructor(private http: HttpClient) {}

getData() {
return this.http.get('https://example.com/api/data').pipe(
timeout(10000), // 10 seconds
catchError((error) => {
// Handle timeout or other errors
console.error('Request timed out or failed', error);
return throwError(error);
})
);
}
}

But, wait, what if I want to add this timeout for all my requests? Do I need to add the timeout operator to every request? The answer is no, you can create an interceptor to add the timeout operator to all requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';

export const DEFAULT_TIMEOUT = new InjectionToken<number>('defaultTimeout');

@Injectable()
export class TimeoutInterceptor implements HttpInterceptor {
constructor(@Inject(DEFAULT_TIMEOUT) protected defaultTimeout: number) {
}

intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(timeout(this.defaultTimeout));
}
}

Don’t forget to add the interceptor to the providers array in the AppModule.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { TimeoutInterceptor } from './timeout.interceptor';

@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: TimeoutInterceptor,
multi: true,
},
{ provide: DEFAULT_TIMEOUT, useValue: 30000 }
],
})
export class AppModule {}

Now, all your HTTP requests will have a timeout of 10 seconds.

But, what if I want to set different timeout values for some specific requests? You can add a custom header to the request and check it in the interceptor, if custom header timeout exists, use the custom timeout value, otherwise use the default timeout value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class TimeoutInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const timeoutValue = req.headers.get('timeout') || this.defaultTimeout;
const timeoutValueNumeric = Number(timeoutValue);

return next.handle(req).pipe(timeout(timeoutValueNumeric));
}
}

Now, you can set the timeout value in the request headers.

1
2
3
4
import { HttpHeaders } from '@angular/common/http';

const headers = new HttpHeaders().set('timeout', '5000'); // 5 seconds
this.http.get('https://example.com/api/data', { headers });

References

  1. https://rxjs.dev/api/index/function/timeout
  2. https://stackoverflow.com/questions/45938931/default-and-specific-request-timeout

Introduction

There are lots of mockXXXOnce functions in Jest API, for example:

These functions are only mocked on the first call, all the subsequent calls will use the original implementations or return the original values.

Be careful when using such functions, it may not be what you want, take the following code as example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export class AppComponent {
title = 'jest-demo';
lastTime = Date.now();

getIdleTime() {
return Date.now() - this.lastTime;
}

getDuration() {
if (this.getIdleTime() > 5 * 60 * 60 * 1000) {
return '5+ hours';
} else if(this.getIdleTime() >= 60 * 60 * 1000) {
return '1 ~ 5 hours';
} else {
return 'in an hour'
}
}
}

Here are the test codes, the first one pass test, but the second one will failed. Do you know why?

1
2
3
4
5
6
7
8
9
10
11
12
13
it(`should return 5+ hours`, () => {
const fixture = TestBed.createComponent(AppComponent);
const component = fixture.componentInstance;
jest.spyOn(component, 'getIdleTime').mockImplementationOnce(() => 6 * 60 * 60 * 1000);
expect(component.getDuration()).toBe('5+ hours');
});

it(`should return 1 ~ 5 hours`, () => {
const fixture = TestBed.createComponent(AppComponent);
const component = fixture.componentInstance;
jest.spyOn(component, 'getIdleTime').mockImplementationOnce(() => 4 * 60 * 60 * 1000);
expect(component.getDuration()).toBe('1 ~ 5 hours');
});

Because the first test case only reach the first if branch in getDuration function, it only call getIdleTime once. So there is no problem.

But the second test case need to reach the second if branch in getDuration function, but the mock only take effect in the first if branch, the second branch will still use the original implementation of getIdleTime function, so the test failed.

To fix this issue, you can use mockImplementation instead of mockImplementationOnce in the second test case.

Nx Library Types by functionality

Feature library

此类library主要负责和业务相关的组件和页面等等。

UI library

此类Library主要是负责和UI相关的功能。

Data access library

此类library主要负责和数据相关的功能,比如和后端API交互,数据处理等。

Utility library

此类library主要负责工具和辅助功能,比如一些通用的函数,服务等。

Nx library types by buildable and publishable

Workspace library(Normal library)

Create without any options, it’s a normal library.

1
nx g @nx/angular:lib libName
  • No ng-packagr file generated.
  • No package.json file generated .
  • No targets/build section in project.json file.

This type of libraries is intended to be used within the monorepo. It was imported by apps or other libraries in the same monorepo. It can’t be builded or published independently.

Buildable library

Create by adding buildable option.

1
nx g @nx/angular:lib libName --buildable
  • Add ng-packagr file to root of the library.
  • Add package.json file to root of the library.
  • name property in package.json is the libName.
  • Add targets/build section in project.json file.
  • Executor of build is: "executor": "@nx/angular:ng-packagr-lite"

Buildable libraries are similar to “publishable libraries” described above. Their scope however is not to distribute or publish them to some external registry. Thus they might not be optimized for bundling and distribution.

Buildable libraries are mostly used for producing some pre-compiled output that can be directly referenced from an Nx workspace application without the need to again compile it. A typical scenario is to leverage Nx’s incremental building capabilities.

Publishable library

Create by adding publishable and importPath option. importPath is the path that the library will be imported from, will be used as name of the package.

1
nx g @nx/angular:lib libName --publishable --importPath=@myorg/libName
  • Add ng-packagr file to root of the library.
  • Add package.json file to root of the library.
  • name property in package.json is the importPath.
  • Add targets/build section in project.json file.
  • Executor of build is: "executor": "@nx/angular:package"

Publishable libraries is intended to be published outside of the monorepo, and can be imported by other projects.

References