DevUI is a team with both design and engineering perspectives, serving Huawei cloud
DevCloudPlatform and huawei internal several background systems, service designers and front-end engineers.


Official website:
devui.design

Ng Component library:
ng-devui(Welcome Star)

The introduction

As a front-end developer, with the continuous development and growth of company business, the business of component functionality, interactive demands will be more and more, utility components between different products or team will be more and more, this time will need to have a component library for support for internal use, can also be based on the existing component extensions or encapsulate some native third-party libraries. This article will teach you how to build your own Angular component library.

Creating a component library

Start by creating an Angular project that manages the presentation and publishing of components, and generate a new project with the following command

ng new <my-project>

After the project is initialized, go to the project and run the following CLI command to initialize the lib directory and configuration and generate a component library skeleton

ng generate library <my-lib> --prefix <my-prefix>

My-lib specifies its own library name, such as devui, and my-prefix is component and directive prefix, such as d-xxx. The default generated directory structure is as follows



In the angular.json configuration file, you can also see a library configuration under projects

"my-lib": {
  "projectType": "library"."root": "projects/my-lib"."sourceRoot": "projects/my-lib/src"."prefix": "dev"."architect": {
    "build": {
      "builder": "@angular-devkit/build-ng-packagr:build"."options": {
          "tsConfig": "projects/my-lib/tsconfig.lib.json"."project": "projects/my-lib/ng-package.json"
      },
  "configurations": {
    "production": {
      "tsConfig": "projects/my-lib/tsconfig.lib.prod.json"}}},...Copy the code

Key Configuration Modification

Directory Layout Adjustment

From the directory structure, we can see that the default generated directory structure is relatively deep. By referring to Material Design, we can customize the directory structure as follows:



Modification description:

  • Delete SRC directory from my-lib and copy test.ts from SRC directory
  • My-lib.module. ts(for managing imports and exports of components) and index.ts(for exporting my-lib.module.ts, to simplify imports)
  • Modify angular.json under my-libsourceRootPath to my-lib

Modified as follows:

// my-lib.module.ts


import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AlertModule } from 'my-lib/alert'; {imports: [CommonModule], exports: [AlertModule], providers: [],})export class MyLibModule {}


// index.ts
export * from './my-lib.module';


//angular.json
"projectType": "library"."root": "projects/my-lib"."sourceRoot": "projects/my-lib"// This path points to our new directory"prefix": "deCopy the code

Library build key configuration

Ng-package. json configuration file, which Angular Library builds on

{
  "$schema": ".. /.. /node_modules/ng-packagr/ng-package.schema.json"."dest": ".. /.. /publish"."lib": {
    "entryFile": "./index.ts"
  },
  "whitelistedNonPeerDependencies": ["lodash-es"]}Copy the code

Key configuration description:

  • Dest, lib build output path, here we change to publish directory, and project build dist directory to distinguish
  • Lib /entryFile, which specifies the library build entryFile, pointing to index.ts above

WhitelistedNonPeerDependencies (optional), if the component library relies on third-party libraries, such as lodash, need to configure white list here, because ng – packagr building in order to avoid the third party rely on libraries there may be multiple versions the risk of conflict, Json dependencies are checked. If no whitelist is configured for dependencies, the build fails.

PeerDependcies are recommended for package.json configuration if the business is also configured with related dependencies

{
  "name": "my-lib"."version": "0.0.1"."peerDependencies": {
    "@angular/common": "^ 9.1.6." "."@angular/core": "^ 9.1.6." "."tslib": "^ 1.10.0"}}Copy the code

For complete configuration, see the Angular documentation github.com/ng-packagr/…

Develop an Alert component

Component Function Description

We refer to the Alert component of the DevUI component library to develop a component to test our component library. The Alert component mainly displays different colors and ICONS according to the type passed in by the user, which is used to display different warning messages to the user. The visual display is as follows



Component structural decomposition

First, let’s take a look at what files the Alert component directory contains



Directory structure description:

  • A component is a complete module (like a normal business module) and contains a unit test file
  • There is a package.json in the component directory to support secondary entry (individual components can be imported on demand)
  • Public-api. ts is used to export modules, components, and services. Index. ts is used to export public-API for other modules

The key contents are as follows:

// package.json
{
  "ngPackage": {
    "lib": {
      "entryFile": "public-api.ts"
    }
  }
}


//public-api.ts
/*
* Public API Surface of Alert
*/
export * from './alert.component';
export * from './alert.module';
Copy the code

Defining input and output

Next we began to implement the component, first we define the Input and Output of the component, the alert content we use the projection of the way to pass in, Input parameter support to specify the type of alert, whether to display ICONS, whether alert can be closed, Output returns to close the callback, used for the user to deal with the logic after closing

import { Component, Input } from '@angular/core'; // Define the optional types of alertexport type AlertType = 'success' | 'danger' | 'warning' | 'info';


@Component({
  selector: 'dev-alert',
  templateUrl: './alert.component.html',
  styleUrls: ['./alert.component.scss'],})exportClass AlertComponent {// Alert @input ()type: AlertType = 'info'; // Whether to display ICONS to support user-defined ICONS @input () showIcon =true; // Whether @input () closeable = can be turned offfalse; // Close @Output() closeEvent: EventEmitter< Boolean > = new EventEmitter< Boolean >(); hide =false;
  constructor() {}


  close(){
    this.closeEvent.emit(true);
    this.hide = true;
  }
Copy the code

Define the layout

The layout consists of a close button, icon placeholder, and content projection. When the component is closed, we clear the DOM.

<div class="dev-alert {{ type }} " *ngIf=! "" hide">
  <button type="button" class="dev-close" (click)="close()" *ngIf="closeable"></button>
  <span class="dev-alert-icon icon-{{ type }}" *ngIf="showIcon"></span>
  <ng-content></ng-content>
</div>Copy the code

At this point, our component’s page layout and component logic are wrapped, and the development is complete based on visual presentation plus the corresponding styling.

Testing the Alert component

Development-referenced components

In the process of component development, we need to be able to debug the logic and adjust the UI display in real time. Open tsconfig.json in the root directory, modify the Paths mapping, so that we can debug our components locally in development mode. Alternatively, you can use the default configuration by ng build my-lib –watch, pointing to the built pre-release file, which will be configured as our modified directory public/my-lib/*

"paths": {
  "my-lib": [
    "projects/my-lib/index.ts"]."my-lib/*": [
    "projects/my-lib/*"],}Copy the code

Once configured, we can use the library we are developing in the application in NPM mode. We import the components we are developing in app.module.ts. Here we can import all components from my-lib.module. Or import our AlertModule directly (already configured to support secondary entry)

import { AlertModule } from 'my-lib/alert';
// import { MyLibModule } from 'my-lib';


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    // MyLibModule
    AlertModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }Copy the code

At this point you can use the Alert component we are developing directly in the app.component.html page

<section> <dev-alert> I am a default type of alert</dev-alert> </section>Copy the code

When you open up the page, you can see what the current development looks like. At this point, you can adjust the style and interaction logic based on how the page looks



Writing unit tests

As mentioned earlier, we have a unit test file. Component development in order to ensure the quality of the code and the stability of the components for subsequent refactoring, when developing components, conditional suggestions are added with unit tests.

As we adjust the directory structure, let’s first modify the configuration

// angular.json
"my-lib": {..."test": {
    "builder": "@angular-devkit/build-angular:karma"."options": {
      "main": "projects/my-lib/test.ts"// This points to the adjusted file path"tsConfig": "projects/my-lib/tsconfig.spec.json"."karmaConfig": "projects/my-lib/karma.conf.js"}},} // tsconfig.spec.json in my-lib"files": [
  "test.ts"// point to the test entry file in the current directory]Copy the code

The following is a simple test reference. It simply tests whether the type of the test is correct. The components to be tested are defined in the direct test file.

import { async, ComponentFixture, TestBed } from '@angular/core/testing';


import { Component } from '@angular/core';
import { AlertModule } from './alert.module';
import { AlertComponent } from './alert.component';
import { By } from '@angular/platform-browser';


@Component({
  template: `
    <dev-alert [type] ="type" [showIcon]= "showIcon"[closeable]="closeable"    (closeEvent)="handleClose($event)"</span> </span> </dev- Alert > '}) class TestAlertComponent {type = 'info';
  showIcon = false;
  closeable = false;
  clickCount = 0;
  handleClose(value) {
    this.clickCount++;
  }
}


describe('AlertComponent', () = > {let component: TestAlertComponent;
  let fixture: ComponentFixture<TestAlertComponent>;
  let alertElement: HTMLElement;


  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [AlertModule],
      declarations: [ TestAlertComponent ]
    })
    .compileComponents();
  }));


  beforeEach(() => {
    fixture = TestBed.createComponent(TestAlertComponent);
    component = fixture.componentInstance;
    alertElement = fixture.debugElement.query(By.directive(AlertComponent)).nativeElement;
    fixture.detectChanges();
  });


  describe('alert instance test', () => {
    it('should create', () => {
      expect(component).toBeTruthy();
    });
  });


  describe('alert type test', () => {
    it('Alert should has info type', () => {
      expect(alertElement.querySelector('.info')).not.toBe(null);
    });


    it('Alert should has success type', () => {// ModifytypeCheck if the type change is correct component.type ='success';
      fixture.detectChanges();
      expect(alertElement.querySelector('.success')).not.toBe(null);
    });
  }Copy the code

Unit tests can be performed by executing ng test my-lib, which by default opens a window showing our test results

At this point, the component development reference, testing is complete, and once the functionality and interaction are in order, it is ready for release to NPM.

More test content refer to the official introduction: angular.cn/guide/testi…

Release the component

After the component is developed and the unit test meets the entrance control index defined by us, it can be ready to be released to NPM for other students to use.

First we build the component library, since the Ivy engine is used by default after NG9. Publishing libraries in Ivy format to the NPM repository is not officially recommended. So before publishing to NPM, we build it using the –prod flag, which uses the old compiler and runtime, the View Engine, instead of Ivy.

ng build my-lib --prodCopy the code



Once the build is successful, you are ready to publish the component library, in this case to the official NPM repository

  1. If you do not have an NPM account, please register an account on the official website, and choose a free account of public type
  2. If there is an account, check whether the configured Registry points to the official NPM Registry registry.npmjs.org/
  3. Run it on the terminalnpm loginLog in to a registered user

npm publish --access public




Docs.npmjs.com/packages-an…


If it is an internal private library, it is ok to configure Registry according to the requirements of the private library, and the issuing commands are the same.


Join us



We are DevUI team, welcome to come here and build elegant and efficient human-computer design/research and development system with us. Email: [email protected].

The text/DevUI June


Previous articles are recommended

Pagination with Vue/React/Angular Frameworks

“How to build a grayscale Publishing environment”

Practice of Micro Front End in Enterprise Application (PART 1)