0%

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

Introduction

今天在做一个 Angular 项目的时候,遇到了一个问题:

1
ERROR in src/app/app.module.ts:1:1 - error TS6059: File 'xxx' is not under 'rootDir' 'yyy'. 'rootDir' is expected to contain all source files.

可以看到这个错误是关于rootDir的,那么rootDir是什么呢?看一下官网的解释:

Default: The longest common path of all non-declaration input files. If composite is set, the default is instead the directory containing the tsconfig.json file.

When TypeScript compiles files, it keeps the same directory structure in the output directory as exists in the input directory.

For example, let’s say you have some input files:

1
2
3
4
5
6
7
8
MyProj
├── tsconfig.json
├── core
│ ├── a.ts
│ ├── b.ts
│ ├── sub
│ │ ├── c.ts
├── types.d.ts

The inferred value for rootDir is the longest common path of all non-declaration input files, which in this case is core/.

If your outDir was dist, TypeScript would write this tree:

1
2
3
4
5
6
MyProj
├── dist
│ ├── a.js
│ ├── b.js
│ ├── sub
│ │ ├── c.js

Importantly, rootDir does not affect which files become part of the compilation. It has no interaction with the include, exclude, or files tsconfig.json settings.

Note that TypeScript will never write an output file to a directory outside of outDir, and will never skip emitting a file. For this reason, rootDir also enforces that all files which need to be emitted are underneath the rootDir path.

重点看这句:rootDir also enforces that all files which need to be emitted are underneath the rootDir path. 也就是说,所有需要被编译的文件都必须在rootDir的路径下,否则就会报错。

比如下面的代码结构,如果我们在tsconfig.json中指定了rootDircore,那么helpers.ts就不在rootDir的路径下,所以会报错。

1
2
3
4
5
6
MyProj
├── tsconfig.json
├── core
│ ├── a.ts
│ ├── b.ts
├── helpers.ts

Nx based mono-repos.

如果是基于Nx的但一代码仓库,有时候也会出现这个错误,原因是一个Publishable的lib引用了另一个Non-Publishable的lib,这时候就会报错。详情请看这里, 关于Nx buildable/publishable libraries, please see here

  1. ngc - Angular compiler
  2. ngcc - Angular compatible compiler
  3. . fems - Flattened ES Module, this is the angular file format for ES Module.
  4. AOT - Ahead of Time Compilation
  5. JIT - Just in Time Compilation
  6. ng add vs npm install - ng add is a schematic that can install packages and run additional code to set up the package. npm install is just installing the package.
  7. Angular HttpClient底层是基于HttpXMLRequest和Jsonp的,所以可以使用XMLHttpRequest的所有方法。
  8. HostBinding - Angular Decorator that declares a DOM property to bind to. is equivalent to [property]=”expression”.
  9. Angular如何处理scss文件的?package.json中并没有安装sass对应的包。难道是Angular CLI内置了sass的编译器,所以不需要安装sass包?
  10. :ng-deep和:host如何配合使用
  11. Angular中的ViewEncapsulation是如何实现的?有哪三种封装模式?
  12. Reload page on same url with different parameters
    1
    2
    3
    4
    5
    6
    7
    8
    export const appConfig: ApplicationConfig = {
    providers: [
    provideRouter(
    appRoutes,
    withRouterConfig({ onSameUrlNavigation: 'reload' })
    ),
    ],
    };
  13. 如何使用Native Federation?
  14. How to mock service in Unit test(Jest) in Angular? https://stackoverflow.com/questions/79071748/mock-provided-service-in-standalone-component-in-jest-unit-test
  15. 这种怎么测试?添加到Jest异步测试中。
    1
    2
    3
    4
    5
    getData() {
    this.dataService.fetchData().subscribe((data: any) => {
    this.products = data;
    });
    }
  16. Generate module based component after Angular 17.
    Start from Angular 17, Angular CLI use standalone component by default, however, you can still generate module based application with the following command.
1
ng g app my-app --standalone=false
  1. Why Angular use Decorator instead of Abstract Class for Component and Service?
  2. Android No matching client found for package name ‘com.jiyuzhai.caoquanbei’, update package name in file app/src/google-services.json to match your application id.
  3. 一个Angular文件内可以定义多个组件。
  4. Event loop and browser rendering是如何交互的,也就是Browser rendering是在Event loop哪个阶段进行的?
  5. NW.js和Electron类似,都是用来开发跨平台桌面应用的框架。
  6. Traceur和Babel都是用来将ES6+代码转换为ES5代码的工具。Traceur是Google开发的,Babel是社区开发的。
  7. RxJS 社区中常见的约定:以 $ 结尾的变量通常表示它是一个 Observable(可观察对象)。
  8. Virtual Dom的好处是什么?为什么要使用Virtual Dom?
    1. performance - 批量更新dom,而不是每次更新都操作dom。
    2. 可以跨平台 - 这点很重要,多数人回到虚拟DOM,都说性能,但是跨平台也很重要,虚拟DOM相当于一个中间层,可以对接不同平台,比如Android,IOS,Desktop,Web等。
    3. 为什么有些框架不用虚拟DOM还更快?这是因为React/Vue等框架的最小单元就是组件,无法做到控件级别的更新,所以只能更新整个组件,这样就会有性能问题。而有些框架是以控件为最小单元,所以可以做到控件级别的更新,这样就会更快。
  9. Angular router可以传参数,用navigationExtras对象中的state.
    1. 在导航时传递参数
    1
    this.router.navigate(['/detail'], { state: { id: 1 } });
    1. 在接收参数的组件中获取参数
    1
    const id = history.state.id; // 不对吧?history 哪来的?
  10. GraphQL playground.
  11. You should never use function in angular template. - url
  12. Directive composition API - search on angular.dev, this is a new feature in angular 15, why this is useful?
  13. javascript, generator functions
  14. default import syntax in ES6.
  15. WeakMap is not iterable, why? TypedArray is not array.
  16. Angular BehaviorSubject vs Subject - BehaviorSubject needs an initial value, Subject does not.
  17. Angular aot can be enabled/disabled in angular.json file.
  18. it.each in Jest - This need real example from project.
  19. Why Jest need transformer? Because Jest only understand JavaScript, so it needs transformer to transform other file types to JavaScript. for example ts -> js, tsx/jsx -> js vue/angular -> js, by default Jest use babel-jest as transformer, you can also use angular-jest-preset for Angular project.
  20. Proxy.revocable(target, handler).
  21. What is cross-site request forgery (CSRF) attack? How to prevent it?
  22. Website defacement - what’s this?
  23. steps to use web component.
  24. Grid layout.
  25. HTML page will always show even it contains errors, the console won’t display html error, it only show javascript errors.
  26. Html中标签的样式按照LVHA的顺序来写,这样可以避免样式覆盖的问题。
    1. :link - 未访问的链接
    2. :visited - 已访问的链接
    3. :hover - 鼠标悬停
    4. :active - 激活状态
  27. css function env - env() 函数可以用来获取环境变量的值,比如获取视口的宽度。
  28. css @support operator - @supports 操作符用来检测浏览器是否支持某个CSS属性。
  29. video tag: source vs src - What’s the differences?
  30. CSS attributes selectors - ^, $, *, ~ - What’s the differences? [attr*=value] vs [attr~=value].
  31. compare in javascript:
    1. ==
    2. ===
    3. Object.is
    4. SameValueZero - MDN
  32. chunk load error: https://rollbar.com/blog/javascript-chunk-load-error/
  33. drag and drop:
    1. https://material.angular.io/cdk/drag-drop/overview
    2. https://github.com/swimlane/ngx-dnd
    3. https://packery.metafizzy.co/
  34. What’s the differences between markForCheck() and detectChanges() in Angular? - stackoverflow
  35. SASS vs SCSS
    1. SASS - Syntactically Awesome Style Sheets
    2. SCSS - Sassy CSS
    3. SASS is the older syntax, it use indentation to define blocks, SCSS is the newer syntax, it use curl braces to define
  36. 如何查看JS编译后的代码?比如变量提升后的代码,Google的V8引擎可以实现这个功能。而Node底层也是V8,所以用Node命令可以查看,但是输出的代码不是很直观。需要进一步研究,参考资料:https://medium.com/@drag13dev/https-medium-com-drag13dev-how-to-get-javascript-bytecode-from-nodejs-7bd396805d30
  37. JavaScript中有些时候使用new和不使用new结果完全不同,比如:
    1. Date() vs new Date(): Date()返回的是字符串,new Date()返回的是Date对象。
    2. String() vs new String(): String()返回的是字符串,new String()返回的是String对象。- 在这里, 不加new其实相当于类型转换
    3. Number() vs new Number(): Number()返回的是数字,new Number()返回的是Number对象。- 同上
    4. 注意:对于Array,加不加new,结果一样,都是数组。
  38. Harmony Import/Export - 其实就是ES6中的Import/Export,这是ES6的模块化规范,可以用来导入导出模块。参考这里:https://en.wikipedia.org/wiki/ECMAScript_version_history#4th_Edition_(abandoned) 和这里:https://stackoverflow.com/questions/52871611/what-is-harmony-and-what-are-harmony-exports
  39. Immutable.js - 一个用来处理不可变数据的库,可以用来提高性能。参考这里:https://immutable-js.com/
  40. 性能优化好文 - https://zhuanlan.zhihu.com/p/41292532
  41. html中的checkbox/radio button默认的样式由浏览器决定,不是十分好定制化,如果只是更改颜色的话,可以使用accent-color属性,如果要更改大小的话,可能就需要其他手段了。比如这里:https://stackoverflow.com/questions/4148499/how-to-style-a-checkbox-using-css/69164710#69164710
  42. 有哪些样式是CSS改变不了的?
    1. select控件中option的高亮色,就是鼠标划过每个选项时的高亮色,这个一般是蓝色,且不能更改。如果想改,那么需要使用ul/li模拟select控件。
  43. 浏览器如何处理双击,双击的时候,浏览器会选取最近的能选到的文本,所以随便打开一个网站,找个空白处双击一下,你会发现有文本被选中了。如何禁止这种行为呢?
    以下方法会禁止所有选中文本的行为,包括用户主动选择的,要找到最近的node,手动设置一下。
1
2
3
function disableTextSelection() {
document.addEventListener('selectstart', (e) => e.preventDefault());
}

Install TypeScript

This directive will install TypeScript globally.

1
npm install -g typescript

Init TypeScript project

This step will create a tsconfig.json file in under typescript-tsc-guide folder.

1
2
3
mkdir typescript-tsc-guide
cd typescript-tsc-guide
tsc --init # create tsconfig.json file

Create typescript files

Create a folder src under current folder. Then create a file index.ts under src folder.

1
2
mkdir src
touch src/index.ts

input the following code to src/index.ts file.

1
const add = (a, b) => a + b;

Compile TypeScript files

This step will compile the src/index.ts file to src/index.js file.

1
2
cd typescript-tsc-guide
tsc

Compile options

outDir

Usually, we put the emitted files under dist folder. To do this, we need to modify the tsconfig.json file.

1
2
3
4
5
{
"compilerOptions": {
"outDir": "./dist" // output directory for the emitted files
}
}

Run tsc again, the emitted files will be under dist folder.

rootDir

We can also specify the source folder by using rootDir option, in this way, only files under the source folder will be compiled.

1
2
3
4
5
6
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src" // specify the source folder
}
}

If you specify the rootDir option, you can’t put the source files outside the source folder. otherwise you’ll got the following error:

1
error TS6059: File '/typescript-tsc-guide/xxx.ts' is not under 'rootDir' '/typescript-tsc-guide/src'. 'rootDir' is expected to contain all source files.

To test this, create a file test.ts under project root(same location as tsconfig.json) and run tsc command, you’ll get the error.

Angular 隐藏了项目构建的细节,其构建过程通过各种Builder来实现,底层可以使用Webpack或者ESBuild(从Angular17开始,默认使用ESBuild),那么我们需要修改Webpack配置怎么办呢?其实只有一个办法,那就是通过第三方npm package,这里介绍目前流行的两个。

这两者的实现原理也比较简单,都是通过继承@angular-devkit/build-angular来实现的。

下面我们分别看看,这两个package的使用方法。

ngx-build-plus

Installation

1
npm install ngx-build-plus --save-dev

Usage

angular.json(for Angular project) or project.json(For Nx based mono repo)

1
2
3
4
5
6
7
8
9
10
{
"architect": {
"build": {
"builder": "ngx-build-plus:browser", // change builder
"options": {
"extraWebpackConfig": "extra-webpack.config.js" // supply a path to the custom webpack config
}
}
}
}

@angular-builders/custom-webpack

Installation

1
npm install @angular-builders/custom-webpack --save-dev

Usage

angular.json(for Angular project) or project.json(For Nx based mono repo)

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser", // change builder
"options": {
"customWebpackConfig": {
"path": "./extra-webpack.config.js", // supply a path to the custom webpack config
"mergeStrategies": { "externals": "replace" }
}
}
}
}
}

How to choose?

  1. If you are using Angular Module Federation, you should use ngx-build-plus, and it is installed automatically when you create a new Angular project with Angular Module Federation. This package has not been upgraded for a long time, but it is still working. ngx-build-plus support hooks.
  2. If you are not using Angular Module Federation, you can choose either of them, but @angular-builders/custom-webpack is more popular. This package is upgraded frequently.