DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Low-Code Development: Leverage low and no code to streamline your workflow so that you can focus on higher priorities.

DZone Security Research: Tell us your top security strategies in 2024, influence our research, and enter for a chance to win $!

Launch your software development career: Dive head first into the SDLC and learn how to build high-quality software and teams.

Open Source Migration Practices and Patterns: Explore key traits of migrating open-source software and its impact on software development.

Related

  • The Most Popular Angular UI Libraries To Try in 2021
  • The Power of @ngrx/signalstore: A Deep Dive Into Task Management
  • React, Angular, and Vue.js: What’s the Technical Difference?
  • Building Angular Library and Publishing in npmjs Registry

Trending

  • Documenting a Spring REST API Using Smart-doc
  • How To Use Thread.sleep() in Selenium
  • Application Telemetry: Different Objectives for Developers and Product Managers
  • 10 ChatGPT Prompts To Boost Developer Productivity
  1. DZone
  2. Coding
  3. Frameworks
  4. Developing and Using Angular Libraries

Developing and Using Angular Libraries

A developer gives a tutorial on how to create, configure, and use an Angular library, including all the TypeScript code you will need to get going.

By 
Sven Loesekann user avatar
Sven Loesekann
·
Mar. 05, 21 · Tutorial
Like (3)
Save
Tweet
Share
5.2K Views

Join the DZone community and get the full member experience.

Join For Free

In the AngularAndSpring project charts are displayed. To display the charts I have developed the ngx-simple-charts library. The design goal is to be D3.js based, have a typesafe Angular API, and be so simple and small that it can be migrated fast to new Angular versions. Currently the library supports line charts, bar charts might be supported in the future. 

Creating and Configuring the Angular Library

The Angular CLI can generate a library shell with this command: ng generate library <library_name>

In the library shell you get a Module, a Component, and a Service. The library API is defined in the public-api file. 

To add the D3 charts, the libraries and typings need to be added in the package.json:

JSON
 




x


 
1
"dependencies": {
2
  ...
3
  "d3-array": "^2.9.1",
4
  "d3-axis": "^2.0.0",
5
  "d3-brush": "^2.1.0",
6
  "d3-color": "^2.0.0",
7
  "d3-format": "^2.0.0",
8
  "d3-interpolate": "^2.0.1",
9
  "d3-scale": "^3.2.3",
10
  "d3-selection": "^2.0.0",
11
  "d3-shape": "^2.0.0",
12
  "d3-time-format": "^3.0.0",
13
  "d3-transition": "^2.0.0"
14
},
15
"devDependencies": {
16
  ...
17
  "@types/d3-array": "^2.8.0",
18
  "@types/d3-brush": "^2.1.0",
19
  "@types/d3-color": "^2.0.1",
20
  "@types/d3-format": "^2.0.0",
21
  "@types/d3-interpolate": "^2.0.0",
22
  "@types/d3-scale": "^3.2.2",
23
  "@types/d3-selection": "^2.0.0",
24
  "@types/d3-shape": "^2.0.0",
25
  "@types/d3-time-format": "^3.0.0",
26
  "@types/d3-transition": "^2.0.0",    
27
  "@types/d3-axis": "^2.0.0"
28
}


The scripts part of the package.json has these additions:

JSON
 




xxxxxxxxxx
1


 
1
"scripts": {
2
  ...
3
  "build-watch": "ng build --watch",
4
  "build-prod": "ng build --configuration=production"
5
}


The 'build-watch' script rebuilds the library on change. That helps development.

The 'build-prod' script builds the library with production settings that are needed to publish it on npm. According to the Angular team, libraries should not be published with Ivy enabled.

In the library folder, the package.json needs to get the library dependencies:

JSON
 




xxxxxxxxxx
1
18


 
1
  "peerDependencies": {    
2
    "@angular/common": "^11.1.0",
3
    "@angular/core": "^11.1.0"
4
  },
5
  "dependencies": {
6
    "d3-array": "^2.9.1",
7
    "d3-axis": "^2.0.0",
8
    "d3-brush": "^2.1.0",
9
    "d3-color": "^2.0.0",
10
    "d3-format": "^2.0.0",
11
    "d3-interpolate": "^2.0.1",
12
    "d3-scale": "^3.2.3",
13
    "d3-selection": "^2.0.0",
14
    "d3-shape": "^2.0.0",
15
    "d3-time-format": "^3.0.0",
16
    "d3-transition": "^2.0.0",
17
    "tslib": "^2.0.0"
18
  }


The 'peerDependencies' are expected from the project that uses the library.

The 'dependencies' are used to build the library.

The ng-package.json has the umd module config and the whitelisted dependencies:

JSON
 




xxxxxxxxxx
1
17


 
1
"lib": {
2
    "entryFile": "src/public-api.ts",
3
    "umdModuleIds": {
4
      "d3-array": "d3-array",
5
      "d3-brush": "d3-brush",
6
      "d3-color": "d3-color",
7
      "d3-format": "d3-format",
8
      "d3-interpolate": "d3-interpolate",
9
      "d3-scale": "d3-scale",
10
      "d3-selection": "d3-selection",
11
      "d3-shape": "d3-shape",
12
      "d3-time-format": "d3-time-format",
13
      "d3-transition": "d3-transition",
14
      "d3-axis": "d3-axis"
15
    }
16
  },
17
  "whitelistedNonPeerDependencies": ["d3"]


If the 'umdModuleIds' are not provided, Angular will guess at them and the 'd3' dependencies need to be whitelisted.

Implementing the Charts in the Library

The component and the service have been moved to their own folders. To add the charts to the component the template has been updated:

HTML
 




xxxxxxxxxx
1


 
1
<div class="d3-chart-wrapper">
2
  <svg #svgchart class="d3-chart"></svg>
3
</div>


The CSS classes offer some styling options and make the chart use all the space that is available in the div.

The SVG tag is the host element with the 'svgchart' reference variable of the chart that is generated with the D3 library.

The SCSS file makes the chart file the available space and provides some basic styling:

SCSS
 




xxxxxxxxxx
1
21


 
1
.d3-chart {
2
    display: flex;
3
    justify-content: center;
4
    align-items: center;
5
    width: 100%;
6
    height: 100%;
7
}
8

          
9
.d3-chart-wrapper {
10
    display: flex;
11
    justify-content: center;
12
    align-items: center;
13
    width: 100%;
14
    height: 100%;
15
}
16

          
17
::ng-deep .line {
18
    fill: none;
19
    stroke: #1E90FF;
20
    stroke-width: 3;
21
}
22
...


The 'd3-chart' and 'd3-chart-wrapper' classes can be used to change the chart size.

The 'line' class is used to set the line style.

The chart component is setup like this:

TypeScript
 




x
35


 
1
@Component({
2
    selector: 'sc-line-chart',
3
    templateUrl: './sc-line-chart.component.html',
4
    styleUrls: ['./sc-line-chart.component.scss'],
5
    encapsulation: ViewEncapsulation.Emulated,
6
})
7
export class ScLineChartComponent implements AfterViewInit, OnChanges {
8
    @ViewChild("svgchart", {static: true})
9
    private chartContainer!: ElementRef;
10
    private d3Svg!: Selection<ContainerElement, ChartPoint, HTMLElement, any>;
11
    @Input()
12
    private chartPoints: ChartPoints[] = [];
13
    private gAttribute!: Selection<SVGGElement, ChartPoint, HTMLElement, any>;
14
    private gxAttribute!: Selection<SVGGElement, ChartPoint, HTMLElement, any>;
15
    private gyAttribute!: Selection<SVGGElement, ChartPoint, HTMLElement, any>;
16
    private gPathAttribute!: Selection<SVGPathElement, ChartPoint, HTMLElement, any>;
17

          
18
    ngAfterViewInit(): void {
19
        this.d3Svg = select<ContainerElement, ChartPoint>(this.chartContainer.nativeElement);
20

          
21
        this.gxAttribute = this.d3Svg.append('g');
22
        this.gxAttribute = this.gxAttribute.attr('class', 'x axis');
23
        this.gyAttribute = this.d3Svg.append('g');
24
        this.gyAttribute = this.gyAttribute.attr('class', 'y axis');
25
        this.gPathAttribute = this.d3Svg.append('path');
26

          
27
        this.updateChart();
28
    }
29

          
30
    ngOnChanges(changes: SimpleChanges): void {
31
        if (!!changes['chartPoints'] && !changes['chartPoints'].isFirstChange()) {
32
            this.updateChart();
33
        }
34
    }
10
    private d3Svg!: Selection<ContainerElement, ChartPoint, HTMLElement, any>;


In lines 1-6, the 'sc-line-chart' component is defined.

In line 7, the interfaces AfterViewInit and OnChanges are added.

In lines 8-9, the SVG element ref is added with the ViewChild annotation that references the template variable. The 'static' option has to be set to get SVG element ref.

In line 10, the d3Svg selection is defined to get the D3 representation of the element ref.

In lines 11-12, the input component parameter chartPoints is defined. That is expected to contain the chart values in the ChartPoints format.

In lines 13-16, the D3 attributes for the x axis, the y axis, and the chart path are defined.

In lines 18-19, the method ngAfterViewInit initializes the d3svg property with the SVG element ref. The ngAfterViewInit method needs to be used because the ViewChild annotated value has to be availiable.

In lines 21-25, the x axis and the y axis are set with its CSS classes and the path is initialized.

In line 27, updateChart is called to render the chart.

In line 30, the ngChanges method is used to get notified of updates of the component parameters. 

In lines 31-33, the method checks for changes to the chartPoints component parameter and filters out the first change. It calls then the updateChart method to render the chart.

To render the charts the updateChanges method is used:

TypeScript
 




x
1
56
1
56


 
1
    private updateChart(): void {
2
        const contentWidth = isNaN(parseInt(this.d3Svg.style('width')
3
            .replace(/[^0-9\.]+/g, ''), 10)) ? 
4
            0 : parseInt(this.d3Svg.style('width')
5
            .replace(/[^0-9\.]+/g, ''), 10);
6
        const contentHeight = isNaN(parseInt(this.d3Svg.style('height')
7
            .replace(/[^0-9\.]+/g, ''), 10)) ? 
8
            0 : parseInt(this.d3Svg.style('height')
9
            .replace(/[^0-9\.]+/g, ''), 10);
10
        this.d3Svg.attr("viewBox", [0, 0, contentWidth, contentHeight] as any)
11
        if (contentHeight < 1 || contentWidth < 1 || !this.chartPoints 
12
            || this.chartPoints.length === 0 || !this.chartPoints[0].chartPointList 
13
            || this.chartPoints[0].chartPointList.length === 0) {
14
            console.log(`contentHeight: ${contentHeight} contentWidth: ${contentWidth} chartPoints: ${this.chartPoints.length}`);
15
            return;
16
        }
17
        //console.log(`chartPoints: ${this.chartPoints.length} chartPointList: ${this.chartPoints[0].chartPointList.length}`);
18
        
19
        let xScale: ScaleTime<number, number, never> | ScaleLinear<number, number, never>;
20
        if (this.chartPoints[0].chartPointList[0].x instanceof Date) {
21
            xScale = scaleTime()
22
                .domain(extent(this.chartPoints[0].chartPointList, p => p.x as Date) as [Date, Date])
23
                .range([0, contentWidth - this.chartPoints[0].yScaleWidth])
24
        } else {
25
            xScale = scaleLinear()
26
                .domain([0, this.chartPoints[0].chartPointList.length - 1]).nice()
27
                .range([0, contentWidth - this.chartPoints[0].yScaleWidth]);
28
        }
29
30
        //console.log(xScale);
31
32
        const yScale = scaleLinear()
33
            .domain(extent<ChartPoint, number>(this.chartPoints[0].chartPointList, p => p.y) as [number, number]).nice()
34
            .range([contentHeight - this.chartPoints[0].xScaleHeight, 0]);
35
36
        const myLine = line()
37
            .defined(p => (p as unknown as ChartPoint).y !== null && !isNaN((p as unknown as ChartPoint).y))
38
            .x((p,i) => xScale((p as unknown as ChartPoint).x instanceof Date ? 
39
                (p as unknown as ChartPoint).x as Date: i))
40
            .y((p) => yScale((p as unknown as ChartPoint).y))
41
            .curve((p) => curveMonotoneX(p));
42
        
43
        
44
        this.gPathAttribute.datum(this.chartPoints[0].chartPointList)
45
            .attr('transform', 'translate('+ this.chartPoints[0].yScaleWidth +', 0)')
46
            .attr('class', 'line').attr('d', myLine as any);
47
48
        this.gxAttribute
49
            .attr("transform", "translate(" + (this.chartPoints[0].yScaleWidth) + "," 
50
                + (contentHeight - this.chartPoints[0].xScaleHeight) + ")")
51
            .call(axisBottom(xScale));
52
53
        this.gyAttribute
54
            .attr("transform", "translate(" + (this.chartPoints[0].yScaleWidth) + "," + 0 + ")")
55
            .call(axisLeft(yScale));        
56
    }


In lines 2-9, the width and the height are read out from the SVG element. 

In line 10, the viewbox is set for the SVG element.

In lines 11-16, the input values are validated.

In lines 19-28, the x scale is generated. It expects either a Date or a String value.

In lines 32-34, the y scale is generated. It expects a number value.

In lines 36-41, the chart line is generated. It supports the data types of the scales and filters out invalid values. The space between data points is filled by the curveMonotoneX(...) function.

In lines 44-46, the chart line is added to the SVG element with the line class for styling.

In lines 47-51, the x scale is added to the SVG element.

In lines 52-55, the y scale is added to the SVG element.

The Library Interface

To use the library it needs a typesafe interface. That is defined in the Module and the public-api file:

TypeScript
 




xxxxxxxxxx
1


 
1
export * from './lib/service/ngx-simple-charts.service';
2
export * from './lib/sc-line-chart/sc-line-chart.component';
3
export * from './lib/ngx-simple-charts.module';
4
export * from './lib/model/chart-points';


In lines 1-4, the typescript files that should be exported are declared. They contain the public interface of the library. 

The Angular library module file:

TypeScript
 




xxxxxxxxxx
1


 
1
@NgModule({
2
  declarations: [ScLineChartComponent],
3
  imports: [CommonModule],
4
  exports: [ScLineChartComponent],
5
  providers: [NgxSimpleChartsService]
6
})
7
export class NgxSimpleChartsModule { }


In lines 1-6, the Angular Module of the library is defined. It has the chart component, a service, and imports the Angular CommonModule that is needed in the chart component. The chart component is exported to be used by Angular projects.

The TypeScript types for the chart component are in the chart-point file:

TypeScript
 




xxxxxxxxxx
1
11


 
1
export interface ChartPoint{
2
    y: number;
3
    x: string | Date;
4
}
5

          
6
export interface ChartPoints {
7
    name: string;
8
    yScaleWidth: number;
9
    xScaleHeight: number;
10
    chartPointList: ChartPoint[]; 
11
}


The ChartPoints interface defines the chart name, the scale widths, and the chartPointList array. 

Using the Library

The AngularAndSpring project uses the ngx-simple-charts library. During the development of the library the npm link feature can be used. The ngx-simple-charts/dist/ngx-simple-charts/ directory can be linked into the project that uses the library. Then, 'npm run build-watch' rebuilds the library if the code is changed. 

Add the library to the package.json:

JSON
 




xxxxxxxxxx
1


 
1
"dependencies": {
2
    ...
3
    "ngx-simple-charts": "^0.0.3"
4
}


Install the library.

The library module is added to the details.module: 

TypeScript
 




xxxxxxxxxx
1
19


 
1
@NgModule({
2
  imports: [
3
    CommonModule,
4
    FormsModule,
5
    ReactiveFormsModule,
6
    MatToolbarModule,
7
    MatRadioModule,
8
    MatButtonModule,
9
    DetailsRoutingModule,
10
    NgxSimpleChartsModule,
11
  ],
12
  declarations: [
13
    IbdetailComponent,
14
    CbdetailComponent,
15
    BsdetailComponent,
16
    BfdetailComponent
17
  ]
18
})
19
export class DetailsModule { }


The imported Modules now include NgxSimpleChartsModule in line 10. The module declares the detail components to show the data and charts of the currencies.

The AngularAndSpring project has four components that display charts. The components have the common base class detail-base:

TypeScript
 




xxxxxxxxxx
1
17


 
1
export abstract class DetailBase {
2
    chartPoints: ChartPoints[] = [];
3
    utils = new CommonUtils();
4
    currPair = '';
5
    timeframe = this.utils.timeframes[0];
6
    readonly yScaleWidth = 50;
7
    readonly xScaleHeight = 20;
8

          
9
    constructor(protected locale: string) {}
10

          
11
    protected updateChartData(values: Tuple<string, number>[]): void {
12
        const myChartPoint = values.map(myCP => ({x: new Date(myCP.A), y: myCP.B} as ChartPoint));
13
        this.chartPoints = [{name: this.currPair, chartPointList: myChartPoint,
14
            yScaleWidth: this.yScaleWidth, xScaleHeight: this.xScaleHeight} as ChartPoints];
15
        //console.log(this.chartPoints);
16
    }
17
}


In lines 1-7, the abstract class with the common properties is defined.

In line 11, the method updateChartData is defined with its common interface of Tuples.

In line 12, the chart points in the tuples are mapped in the ChartPoint interface array.

In lines 13-14,  an array of the ChartPoints interface is created for the ngs-simple-charts library. The library expects a ChartPoints array to be able to support multi-line charts in the future.

The 'sc-line-chart' component is used in the template bfdetail.component.html: 

HTML
 




xxxxxxxxxx
1


 
1
        <div #chartContainer class="chart-container">           
2
            <sc-line-chart [chartPoints]="chartPoints"></sc-line-chart>         
3
        </div>   


In line 2, the sc-line-chart component of the library is used and the chart points are provided by the DetailBase class that the component class BfdetailComponent extends.

Conclusion

Creating and publishing the ngx-simple-charts library took some time. The Angular CLI helped a lot by generating a base. The npm link feature help in the development process. The implementation is very close to the examples of D3.js. There are examples of other Angular libraries that can be used for ideas. With all that help it was possible to build this library quite fast and easier than expected. A thank you to the Angular Team for the user friendly library architecture and CLI support. 

The Future of ngx-simple-charts

The library enables developer to quickly update to new Angular versions. It is intended that it will stay small and with few features. In the future there might be more features, but only if that does not interfere with the fast updates goal. There are no resources to make it a fully featured chart library.

Library AngularJS Chart

Opinions expressed by DZone contributors are their own.

Related

  • The Most Popular Angular UI Libraries To Try in 2021
  • The Power of @ngrx/signalstore: A Deep Dive Into Task Management
  • React, Angular, and Vue.js: What’s the Technical Difference?
  • Building Angular Library and Publishing in npmjs Registry

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: