Angular Library 快速入门

2019-11-05 16:09:58 浏览数 (1)

新建 Workspace

代码语言:javascript复制
$ ng new sf-lib-app
$ cd sf-lib-app
$ ng serve

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

代码语言:javascript复制
"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 文件夹。
代码语言:javascript复制
"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 参数:

代码语言:javascript复制
$ 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 中导出新建的组件:

代码语言:javascript复制
export * from './lib/button/button.component';

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

代码语言:javascript复制
/*
 * 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 库:

代码语言:javascript复制
$ 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 文件:

代码语言:javascript复制
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

0 人点赞