新建 Workspace
代码语言:javascript复制$ ng new sf-lib-app
$ cd sf-lib-app
$ ng serve
在介绍如何创建 Angular Library 之前,让我们来看一下 Angular 新的配置文件 —— angular.json。早期版本的 angular-cli.json 文件已经被替换为 angular.json 文件,文件的内容也发生了改变。这里我们关心的 projects
属性,它为每个独立的项目提供了一个入口:
"projects": {
"sf-lib-app": {
...
},
"sf-lib-app-e2e": {
...
}
},
这里我们已经有两个项目:
- sf-lib-app:应用目录;
- sf-lib-app-e2e:集成 end-to-end 测试。
创建 sf-lib 库
代码语言:javascript复制$ 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
文件夹。
"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 信息:
代码语言:javascript复制"compilerOptions": {
"paths": {
"sf-lib": [
"dist/sf-lib"
],
"sf-lib/*": [
"dist/sf-lib/*"
]
}
}
当完成 Angular 库开发后,我们可以通过以下命令进行库的构建:
代码语言:javascript复制$ ng build --prod sf-lib
小伙伴们,在构建 Library 时,记得始终添加 —prod 标志。
在应用中使用 sf-lib 库
代码语言:javascript复制import { SfLibModule } from "sf-lib";
以上代码能正常导入 Library,是因为 Angular CLI 会优先从 tsconfig.json
的 paths 属性中查找,然后再 node_modules
中查找。
此时的 app.module.ts 文件内容如下:
代码语言:javascript复制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 默认创建的组件:
代码语言:javascript复制<sf-sf-lib></sf-sf-lib>
通常情况下,我们会删除默认创建的组件,然后创建自定义组件,下面我们就来介绍如何为 sf-lib 创建自定义组件。
创建 sf-lib 组件
相信 ng generate
命令对于使用过 Angular CLI 的同学来说,都不会陌生。要为 sf-lib 库创建自定义组件,我们也可以使用该命令,唯一需要注意的是就是需要设置 --project
参数:
$ ng generate component button --project=sf-lib
接着从 sf-lib 模块中导出组件:
代码语言:javascript复制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
中导出新建的组件:
export * from './lib/button/button.component';
此时我们 public_api.ts
入口文件的内容如下:
/*
* 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 库:
$ ng build --prod sf-lib
sf-lib 重新构建成功后,我们就可以在模板中使用刚创建的 ButtonComponent 组件:
代码语言:javascript复制<sf-sf-lib></sf-sf-lib>
<sf-button></sf-button>
创建 sf-lib 服务
除了创建自定义组件之外,我们也可以创建自定义服务:
代码语言:javascript复制$ ng g service data --project=sf-lib
以上命令成功执行后,将在 sf-lib/lib/src
目录下生成一个 data.service.ts 文件:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
}
假设我们的 DataService 需要利用 HttpClient 从网络上获取对应的数据,这时我们就需要在 SfLibModule 模块中导入 HttpClientModule 模块,且在 DataService 注入 HttpClient 服务:
代码语言:javascript复制import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
@Injectable({
providedIn: "root"
})
export class DataService {
constructor(private http: HttpClient) {}
}
在实际开发中,我们可能需要能够灵活配置 DataService 服务中,请求服务器的地址。这里使用过 Angular Router 模块的同学,可能已经想到了解决方案:
代码语言:javascript复制@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 的定义如下:
代码语言:javascript复制export interface SfLibConfig {
dataUrl: string;
}
export const SfLibConfigService = new InjectionToken<SfLibConfig>(
"TestLibConfig"
);
注册完 SfLibConfigService provider 后,我们需要更新
代码语言:javascript复制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 组件中使用它:
代码语言:javascript复制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 属性,具体如下:
代码语言:javascript复制@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, SfLibModule.forRoot({
dataUrl: `https://jsonplaceholder.typicode.com/todos/1`
})],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
以上代码成功运行后,你将会在控制台看到以下输出信息:
代码语言:javascript复制{userId: 1, id: 1, title: "delectus aut autem", completed: false}
最后在 sf-lib 库开发完成后,我们可以把开发完的库发布到 npm 上:
代码语言:javascript复制$ cd dist/sf-library
$ npm publish
参考资源
- The Angular Library Series - Creating a Library with the Angular CLI