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-lib
sourceRoot
Path 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
- If you do not have an NPM account, please register an account on the official website, and choose a free account of public type
- If there is an account, check whether the configured Registry points to the official NPM Registry registry.npmjs.org/
- Run it on the terminal
npm login
Log 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].
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)