qrcode

马上订阅,开启修仙之旅

Angular Library 快速入门

新建 Workspace

1
2
3
$ ng new sf-lib-app
$ cd sf-lib-app
$ ng serve

在介绍如何创建 Angular Library 之前,让我们来看一下 Angular 新的配置文件 —— angular.json。早期版本的 angular-cli.json 文件已经被替换为 angular.json 文件,文件的内容也发生了改变。这里我们关心的 projects 属性,它为每个独立的项目提供了一个入口:

1
2
3
4
5
6
7
8
"projects": {
"sf-lib-app": {
...
},
"sf-lib-app-e2e": {
...
}
},

这里我们已经有两个项目:

  • sf-lib-app:应用目录;
  • sf-lib-app-e2e:集成 end-to-end 测试。

创建 sf-lib 库

1
$ ng generate library sf-lib --prefix=sf

这里我们快速总结一下 ng generate library 命令执行的操作:

  • 在 angular.json 文件中添加 sf-lib 项目;
  • 在 package.json 文件中添加 ng-packagr 依赖;
  • 在 tsconfig.json 文件中添加 sf-lib 库的引用;
  • 在项目中的 projects 目录下创建 sf-lib 文件夹。
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
"sf-lib": {
"root": "projects/sf-lib",
"sourceRoot": "projects/sf-lib/src",
"projectType": "library",
"prefix": "sf",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/sf-lib/tsconfig.lib.json",
"project": "projects/sf-lib/ng-package.json"
},
"configurations": {
"production": {
"project": "projects/sf-lib/ng-package.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/sf-lib/src/test.ts",
"tsConfig": "projects/sf-lib/tsconfig.spec.json",
"karmaConfig": "projects/sf-lib/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/sf-lib/tsconfig.lib.json",
"projects/sf-lib/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}

我们来重点关注以下属性:

  • root —— 指向 library 库的根文件夹;
  • sourceRoot —— library 库实际的源码目录;
  • projectType —— 指定项目的类型;
  • prefix —— 指定组件使用的前缀;
  • architect —— 该对象用于配置 Angular CLI 构建流程,如 build、test 和 lint。

另外在 tsconfig.json 文件中,会自动添加以下 paths 信息:

1
2
3
4
5
6
7
8
9
10
"compilerOptions": {
"paths": {
"sf-lib": [
"dist/sf-lib"
],
"sf-lib/*": [
"dist/sf-lib/*"
]
}
}

当完成 Angular 库开发后,我们可以通过以下命令进行库的构建:

1
$ ng build --prod sf-lib

小伙伴们,在构建 Library 时,记得始终添加 —prod 标志。

在应用中使用 sf-lib 库

1
import { SfLibModule } from "sf-lib";

以上代码能正常导入 Library,是因为 Angular CLI 会优先从 tsconfig.json 的 paths 属性中查找,然后再 node_modules 中查找。

此时的 app.module.ts 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { SfLibModule } from "sf-lib";

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

然后我们在 app.component.ts 组件对应的模板引用 sf-lib 默认创建的组件:

1
<sf-sf-lib></sf-sf-lib>

通常情况下,我们会删除默认创建的组件,然后创建自定义组件,下面我们就来介绍如何为 sf-lib 创建自定义组件。

创建 sf-lib 组件

相信 ng generate 命令对于使用过 Angular CLI 的同学来说,都不会陌生。要为 sf-lib 库创建自定义组件,我们也可以使用该命令,唯一需要注意的是就是需要设置 --project 参数:

1
$ ng generate component button --project=sf-lib

接着从 sf-lib 模块中导出组件:

1
2
3
4
5
6
7
8
9
10
import { NgModule } from "@angular/core";
import { SfLibComponent } from "./sf-lib.component";
import { ButtonComponent } from "./button/button.component";

@NgModule({
imports: [],
declarations: [SfLibComponent, ButtonComponent],
exports: [SfLibComponent, ButtonComponent]
})
export class SfLibModule {}

之后我们还需要在 public_api 中导出新建的组件:

1
export * from './lib/button/button.component';

此时我们 public_api.ts 入口文件的内容如下:

1
2
3
4
5
6
7
8
/*
* Public API Surface of sf-lib
*/

export * from './lib/sf-lib.service';
export * from './lib/sf-lib.component';
export * from './lib/button/button.component';
export * from './lib/sf-lib.module';

这里需要说明的是,对于组件来说:设置 @NgModule 的 exports 属性是为了使得元素可见,而添加到public_api.ts 入口文件是为了使得 Class 可见。在完成新建 ButtonComponent 组件的导出工作后,我们需要使用下列命令,重新构建 sf-lib 库:

1
$ ng build --prod sf-lib

sf-lib 重新构建成功后,我们就可以在模板中使用刚创建的 ButtonComponent 组件:

1
2
<sf-sf-lib></sf-sf-lib>
<sf-button></sf-button>

创建 sf-lib 服务

除了创建自定义组件之外,我们也可以创建自定义服务:

1
$ ng g service data --project=sf-lib

以上命令成功执行后,将在 sf-lib/lib/src 目录下生成一个 data.service.ts 文件:

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

@Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
}

假设我们的 DataService 需要利用 HttpClient 从网络上获取对应的数据,这时我们就需要在 SfLibModule 模块中导入 HttpClientModule 模块,且在 DataService 注入 HttpClient 服务:

1
2
3
4
5
6
7
8
9
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";

@Injectable({
providedIn: "root"
})
export class DataService {
constructor(private http: HttpClient) {}
}

在实际开发中,我们可能需要能够灵活配置 DataService 服务中,请求服务器的地址。这里使用过 Angular Router 模块的同学,可能已经想到了解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@NgModule({
imports: [HttpClientModule],
declarations: [SfLibComponent, ButtonComponent],
exports: [SfLibComponent, ButtonComponent]
})
export class SfLibModule {
static forRoot(config: SfLibConfig): ModuleWithProviders {
return {
ngModule: SfLibModule,
providers: [
{
provide: SfLibConfigService,
useValue: config
}
]
};
}
}

即通过提供 forRoot() 静态方法,让模块的使用方来配置模块中的 provider。示例中 SfLibConfig 接口和 SfLibConfigService token 的定义如下:

1
2
3
4
5
6
7
export interface SfLibConfig {
dataUrl: string;
}

export const SfLibConfigService = new InjectionToken<SfLibConfig>(
"TestLibConfig"
);

注册完 SfLibConfigService provider 后,我们需要更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Injectable, Inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";

import { SfLibConfigService } from "../public_api";

@Injectable({
providedIn: "root"
})
export class DataService {
constructor(
@Inject(SfLibConfigService) private config,
private http: HttpClient
) {}

getData() {
return this.http.get(this.config.dataUrl);
}
}

更新完 DataService 服务,我们来 SfLibComponent 组件中使用它:

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

@Component({
selector: "sf-sf-lib",
template: `
<p>
sf-lib works!
</p>
`,
styles: []
})
export class SfLibComponent implements OnInit {
constructor(private dataService: DataService) {}

ngOnInit() {
this.dataService.getData().subscribe(console.log);
}
}

接着我们在 AppModule 根模块导入 SfLibModule 模块的时候,配置 dataUrl 属性,具体如下:

1
2
3
4
5
6
7
8
9
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, SfLibModule.forRoot({
dataUrl: `https://jsonplaceholder.typicode.com/todos/1`
})],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}

以上代码成功运行后,你将会在控制台看到以下输出信息:

1
{userId: 1, id: 1, title: "delectus aut autem", completed: false}

最后在 sf-lib 库开发完成后,我们可以把开发完的库发布到 npm 上:

1
2
$ cd dist/sf-library
$ npm publish

参考资源


欢迎小伙伴们订阅前端修仙之路,及时阅读 Angular、RxJS、TypeScript 和 Node.js 最新文章。

qrcode