Creating a structural directive in Angular.

Custom structural directives in Angular allow you to define your own HTML syntax and behavior for creating dynamic templates. They are used to manipulate the DOM in a declarative manner, making it easier to read and understand the intended structure and behavior of a template.

Structural directives are recognizable by the * symbol that precedes the directive name. For example, the built-in *ngFor directive is used to loop over a list of items and render them in the template.

To create a custom structural directive, you first need to import the Directive decorator from the @angular/core module and use it to define a class for your directive. The class should implement the OnChanges lifecycle hook, which is called whenever the input bindings of the directive are updated.

import { Directive, OnChanges } from '@angular/core';

@Directive({
  selector: '[appMyDirective]'
})
export class MyDirective implements OnChanges {
  ngOnChanges() {
    // code to handle changes to input bindings
  }
}

In the ngOnChanges method, you can access the updated input bindings through the SimpleChanges object that is passed to the method. You can then use this information to manipulate the DOM as needed.

To use your custom structural directive in a template, you simply add the * symbol before the directive name in the element where you want it to be applied. For example:

<div *appMyDirective>
  ...
</div>

You can also pass input bindings to your directive, just like with any other Angular directive. For example:

<div *appMyDirective="someInputValue">
  ...
</div>

Custom structural directives are a powerful way to extend the capabilities of Angular templates and provide a clean and declarative syntax for complex DOM manipulation. They can help you make your templates more readable and maintainable, and enable you to write reusable and modular code for your Angular applications.

Getting started with date-fns in Angular.

In this post we will learn how to use date-fns in Angular application. But first let’s understand date-fns.

date-fns is a date utility library which provides simple and extensive set of functions to manipulate JavaScript dates.

For example, say we want to present to user a particular date-time based on a time-zone instead of a date based on the system / browser time-zone (typically working with UTC is easy), this is actually difficult to do until unless you are using some library which parses dates into time-zones. date-fns provides nice set of functions to achieve this.

utcToZonedTime(new Date('2018-09-01T16:01:36.386Z'), 'America/Chicago')

Above function parses the UTC date into a date with the specified time-zone, i.e representing a local time in that time-zone.

Now, let’s get stated with the Angular project.

Steps:
  • Create the angular app.
  • Add the project dependencies, like, date-fns.
  • Use the library functions from date-fns.
  • Create a simple pipe service for date-fns to format dates in the template.
Create the angular app

We are going to use the angular guide to create a new angular project.

ng new ng-date-fns

Now we have our angular application ready, let’s try to add the dependencies.

Add the dependencies
npm i bootstrap date-fns --save

Here we are installing few packages, bootstrap (To display the data in a table) and date-fns (JavaScript date utility library).

Using the library
// Library is tree-shakable, import only what is needed.
import format from 'date-fns/format';

format(new Date(), 'MM/dd/yyyy');

Here, one advantage of using date-fns is its tree-shakable, we can just import only what is needed, which compiles to less bundle size!.

Pipe service
import { Pipe, PipeTransform } from '@angular/core';
import format from 'date-fns-tz/format';
import utcToZonedTime from 'date-fns-tz/utcToZonedTime';

@Pipe({
    name: 'datefns'
})
export class DateFnsPipe implements PipeTransform {
    transform(value: any, timeZone: string, pattern: string, defaultText: string = "Invalid date!") {
        if (!value) {
            return defaultText;
        }
        if (timeZone) {
            const zonedDate = utcToZonedTime(value, timeZone);
            return format(zonedDate, pattern, { timeZone });
        }
        return format(value, pattern);
    }
}
// Use the pipe in the template
<td>{{ utcDate | datefns:'Europe/Berlin':'MMM dd yyyy, hh:mm aa' }}</td>

**You would also need to declare the pipe in the module.

I guess thats it…we have started to use the date-fns in Angular project.

Checkout complete code at Github. And don’t forget to like or share, you can also leave a comment!!, thanks.

Export data to excel in Angular.

In this post we will try to learn how to export data (JavaScript array or data) to an excel file using the SheetJS / XLSX library.

Steps:
  • Create the angular app.
  • Add the dependencies, like, xlsx.
  • Create the table in UI.
  • Logic to export data in the table to excel file.
Create the angular app

We are going to use the angular guide to create a new angular project.

ng new ng-xlsx

Now we have our angular application ready, let’s try to add other dependencies to show table data on the app component and also add the xlsx library.

Add the dependencies
npm i bootstrap lodash xlsx --save

Here we are installing few packages, bootstrap (To display the data in a table), lodash (For performing data manipulation), xlsx (SheetJS library)

We also need @types for lodash, so let’s install that too.

npm i @types/lodash --save-dev
Create the data table

First, we need to import the bootstrap CSS framework into our Angular project so that we can see the styled components / table on the page. Open styles.scss file and add below code.

@import './node_modules/bootstrap/scss/bootstrap';

html,
body {
    font-size: 12px !important;
    margin: 0;
    overflow: hidden;
    width: 100vw !important;
    height: 100vh !important;
}

.page-container {
    padding: 20px;
}

Now open the app component template file (app.component.html) and paste below code, which displays our table.

<div class="page-container">
  <h3>
    {{ title }}
  </h3>
  <div class="row">
    <div class="col-md-12">
        <table class="table table-striped">
            <thead>
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">First</th>
                    <th scope="col">Last</th>
                    <th scope="col">Handle</th>
                </tr>
            </thead>
            <tbody>
                <tr *ngFor="let item of users">
                    <th scope="row">{{ item.uid }}</th>
                    <td>{{ item.first }}</td>
                    <td>{{ item.last }}</td>
                    <td>{{ item.handle }}</td>
                </tr>
            </tbody>
        </table>
        <button type="button" class="btn btn-primary" (click)="exportRawDataToExcel(users, title)">Export to Excel</button>
    </div>
  </div>
</div>

And our data in the table is as below, we need to paste it inside the app component script file (app.component.ts).

// Add import statements
import * as XLSX from "xlsx";
import * as _ from "lodash";

// Add below code to the class
title = "ng-xlsx";

users = [
    {
      uid: "1",
      first: "Mark",
      last: "Otto",
      handle: "@mdo",
    },
    {
      uid: "2",
      first: "Jacob",
      last: "Thornton",
      handle: "@fat",
    },
    {
      uid: "3",
      first: "Larry the Bird",
      last: "Thornton",
      handle: "@twitter",
    },
  ];

Run the Angular application.

npm start

Open the app on the browser, http://localhost:4200/ and we should see our table as below.

Table
Export data to excel file

We have our data ready, now let’s write the logic to download this table data to the excel file.

exportRawDataToExcel(tableData: any, fileName: string) {
    if (!tableData || (tableData && tableData.length === 0)) {
      throw new Error("No data to export");
    }
    const wb = XLSX.utils.book_new();
    const data = [];
    const merges = [];
    const headers = [];
    const keys: string[] = _.keys(_.head(tableData));
    let mergeAcrossStartC = 0;
    for (let m = 0; m < keys.length; m++) {
      merges.push({
        s: { r: 0, c: mergeAcrossStartC },
        e: {
          r: 0,
          c: mergeAcrossStartC,
        },
      });
      mergeAcrossStartC = mergeAcrossStartC + 1;
      headers.push(keys[m]);
    }
    data.push(headers);
    for (let j = 0; j < tableData.length; j++) {
      const rowData = [];
      for (let k = 0; k < keys.length; k++) {
        rowData.push(tableData[j][keys[k]]);
      }
      data.push(rowData);
    }
    const ws = XLSX.utils.aoa_to_sheet(data);

    ws["!cols"] = [];
    _.forEach(headers, (val) => {
      ws["!cols"].push({ wpx: 120 });
    });
    ws["!merges"] = merges;
    /**
     * Add worksheet to workbook
     */
    XLSX.utils.book_append_sheet(wb, ws, "Sheet 1");
    XLSX.writeFile(wb, fileName + ".xlsx");
  }

Above code will be overwhelming, so I would suggest to investigate the SheetJS tool and learn few things. But to summarise…

  • The function takes two parameters, first the raw data (JSON / array) and second the file name. The raw data is exported to the excel file and we name the excel file with the given file name in the function parameter.
  • We parse the data to construct into a format that is needed for the xlsx library.
  • Using few functions in the library, like,
    • XLSX.utils.book_new() – Creates a new work book.
    • XLSX.utils.aoa_to_sheet() – Create a new work sheet with the given data (JS array)
    • XLSX.utils.book_append_sheet(wb, ws, “Sheet 1”) – Adds a work sheet to the work book.
    • XLSX.writeFile(wb, fileName + “.xlsx”) – Finally create the excel file.

I guess this is it, when we click the export button below the table we should see a new .xlsx file downloaded with the data as seen in the table!!.

Checkout complete code on Github.

Created my first react application.

I call myself a Web developer but in reality there are many more frameworks and libraries out there that I need to learn so to better myself. One such library is React.

React is very popular, you can tell by looking at the trend below in comparison with Angular.

React vs Angular
So, I started learning react…It’s been few days…and I decided to create my very first react application. It’s that simple.

There are many posts on the web talking about Why react is popular?, two posts I actually like are, this one and this one.

One thing I like about react is its…

Simplicity

Checkout my first react application on Github and let me know what you think!.

I have started another react application called billing to learn more or practise. But I suppose its going to take me some time to finish this application given that I only spend like few hours in a week on side projects on Github.

Export html content to PDF file in Angular using JSPDF.

We already have two posts on using JSPDF in Angular application and also how to export a table to PDF. Check these posts first.

Now to export html content.!

Let’s look at the docs first, and here we have a method called html which takes a HTMLElement as the first arg and few config options as the second arg. So, we can try using this method in our Angular application to export any html content, right?.

Say, for example we have our html content in the template file of our Angular application like below.

<div
    #rentReceiptsDiv
    class="rent-receipts"
    *ngIf="resultsCalculated && !errorModel.errorMsg"
>
    <div class="receipt" *ngFor="let item of receiptDateRanges">
        <div class="row">
            <h3>Rent receipt</h3>
        </div>
        <div class="row pt-2">
            <h6 class="fw-light">
                from {{ item.fromDate | date : 'dd/MM/yyyy' }} to
                {{ item.toDate | date : 'dd/MM/yyyy' }}
            </h6>
        </div>
        <div class="row pt-3">
            <div class="fs-6" [innerHTML]="getDescriptionForPreview(item.fromDate, item.toDate)"></div>
        </div>
        <div class="row pt-5">
            <div class="fs-6">Signature</div>
            <div class="fs-5">{{ _calculatorInputs?.ownerName }}</div>
            <div class="fs-6">{{ _calculatorInputs.ownerPANNumber }}</div>
        </div>
    </div>
</div>

We want to export the above Html content to the PDF file. But the html method in the JSPDF is expecting HTMLElement so to get this object instance of our html content we need to declare the above div element in our component by referencing the id rentReceiptsDiv, so let’s do that.

@ViewChild('rentReceiptsDiv') rentReceiptsDiv: ElementRef;

The ViewChild decorator queries our html template file for the provided id and maintains the reference to it. So, now we have the element reference of our div element that we want to export to PDF. Now, let’s try to export using the html method of the JSPDF library.

downloadReceipts() {
        const fileName = `${this.title}.pdf`;
        const doc = new jsPDF();
        doc.html(this.rentReceiptsDiv.nativeElement as HTMLElement, {
            callback: (d) => {
                d.save(fileName);
            },
            margin: 0,
            autoPaging: true,
            width: 200,
            windowWidth: this.rentReceiptsDiv.nativeElement.clientWidth,
            x: 5,
            y: 0,
        });
    }

As you can see I am making use of the callback function of the html method to export the PDF (save) once it’s ready. I am also passing the div Html element in to the function.

There are also few Html config options you need to be aware of.

Below is the html function definition from the docs.

// jsPDF plugin: html
html(src: string | HTMLElement, options?: HTMLOptions): HTMLWorker;

Also the HTMLOptions definition is as below…We are making use of few of these options, like callback, width, windowWidth. You may have to try few times with these options to get the html rendered correctly.

export interface HTMLOptions {
    callback?: (doc: jsPDF) => void;
    margin?: number | number[];
    autoPaging?: boolean | "slice" | "text";
    filename?: string;
    image?: HTMLOptionImage;
    html2canvas?: Html2CanvasOptions;
    jsPDF?: jsPDF;
    x?: number;
    y?: number;
    width?: number;
    windowWidth?: number;
    fontFaces?: HTMLFontFace[];
}

Checkout complete code base here.

Create Heat map using d3.js with Angular.

Checkout the previous post to get started with d3.js in Angular application.

In this post we will talk about creating a heat map chart using the capabilities of d3.js.

Just like any chart or graph, heat map is also a charting technique to visualize data in two dimensions w.r.t magnitude of an occurrence of an event represented with a color schema, typically the color schema varies from low to high depending on the magnitude.

Heat map

Create a new component.

import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import * as d3 from 'd3v6';
import * as _ from 'lodash';
import {
    HeatMap_ColorRange,
    HeatMap_DomainRange,
    HeatMap_Groups,
    HeatMap_Variables,
} from '../commom/constants';
import { HeatMapDataItemInterface } from '../commom/interfaces/heat-map-data-item.interface';
import { ChartsDataService } from '../commom/services/charts-data.service';

@Component({
    selector: 'app-heat-map',
    templateUrl: './heat-map.component.html',
    styleUrls: ['./heat-map.component.scss'],
})
export class HeatMapComponent implements OnInit {
    @ViewChild('chartContainer', { static: true }) chartContainer: ElementRef;
    @Input() chartId: string = 'heatmap';
    @Input() width: number = 600;
    @Input() height: number = 450;
    _chartData: HeatMapDataItemInterface[];

    private margin = { top: 40, right: 20, bottom: 20, left: 40 };
    private _chart = {
        svg: null,
        mainContainer: null,
        data: [],
        daysCount: 30,
        datesStrAsPerRange: '',
    };

    element!: HTMLElement;

    constructor(private service: ChartsDataService) {
        this._chart.data = service.getHeatMapData();
    }

    ngOnInit(): void {
        this.createHeatmap();
    }

    createHeatmap() {
        // set the dimensions and margins of the graph
        this.width =
            this.chartContainer.nativeElement.getBoundingClientRect().width;
        const widgetWidth =
            (this.chartContainer.nativeElement.getBoundingClientRect().width *
                60) /
            100;
        let heatMapHeight = Math.round(widgetWidth / 3.167);
        this.width = widgetWidth - this.margin.left - this.margin.right;
        this.height = heatMapHeight - this.margin.top - this.margin.bottom;
        const legendWidth = 80;

        /**
         * Remove all the elements in d3 charts if already exist
         */
        d3.select('#' + this.chartId)
            .selectAll('*')
            .remove();
        d3.select('#' + this.chartId + '_legend')
            .selectAll('*')
            .remove();

        /**
         * Mouse over function handler for the treemap nodes
         * Here we highlight the circle with the box shadow css
         */
        const mouseover = function () {
            d3.select(this).style('stroke', 'black');
        };

        /**
         * Mouse out handler
         */
        const mouseout = function () {
            d3.select(this).style('stroke', 'none');
        };

        // append the svg object to the body of the page
        const svg = d3
            .select('#' + this.chartId)
            .append('svg')
            .attr('width', this.width + this.margin.left + this.margin.right)
            .attr('height', this.height + this.margin.top + this.margin.bottom)
            .append('g')
            .attr(
                'transform',
                'translate(' + this.margin.left + ',' + this.margin.top + ')'
            );

        let groupNames = Object.values(HeatMap_Groups);
        let variableNames = Object.values(HeatMap_Variables);

        // Build X scales and axis:
        const x = d3
            .scaleBand()
            .range([0, this.width])
            .domain(groupNames)
            .padding(0.05);
        svg.append('g')
            .style('font-size', 12)
            .style('color', '#ADADAD')
            .attr('transform', 'translate(0, -5)')
            .call(d3.axisTop(x).tickSize(0))
            .select('.domain')
            .remove();

        // Build Y scales and axis:
        const y = d3
            .scaleBand()
            .range([this.height, 0])
            .domain(variableNames)
            .padding(0.05);
        svg.append('g')
            .style('font-size', 12)
            .style('color', '#ADADAD')
            .attr('transform', 'translate(-5, 0)')
            .call(d3.axisLeft(y).tickSize(0))
            .select('.domain')
            .remove();

        const myColor = d3
            .scaleLinear<string>()
            .range(HeatMap_ColorRange)
            .domain(HeatMap_DomainRange);

        // add the squares
        const cards = svg
            .selectAll()
            .data(this._chart.data, function (d) {
                return d.group + ':' + d.variable;
            })
            .enter()
            .append('rect')
            .attr('x', function (d) {
                return x(d.group);
            })
            .attr('y', function (d) {
                return y(d.variable);
            })
            .attr('rx', 4)
            .attr('ry', 4)
            .attr('value', function (d) {
                return d.value;
            })
            .attr('width', x.bandwidth())
            .attr('height', y.bandwidth())
            .attr('fill', 'white')
            .style('fill', function (d) {
                if (d.value === null || isNaN(d.value)) {
                    return '#C3C3C3';
                }
                return myColor(d.value);
            })
            .style('stroke-width', 2)
            .style('stroke', 'none')
            .style('opacity', 1)
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('mouseleave', mouseout);

        // append the legend svg object to the body of the page
        const legend = d3
            .select('#' + this.chartId + '_legend')
            .append('svg')
            .attr(
                'transform',
                'translate(' +
                    +(
                        this.width -
                        HeatMap_ColorRange.length * legendWidth +
                        this.margin.left
                    ) +
                    ',0)'
            )
            .attr('width', legendWidth * HeatMap_ColorRange.length)
            .attr('height', 35);

        const domainRange: string[] = HeatMap_DomainRange.map(
            (item, index, items) => {
                const domain = '' + item;
                if (index === 0) {
                    return '>' + domain;
                } else if (index === items.length - 1) {
                    return '<' + domain;
                }
                return '';
            }
        );
        legend
            .selectAll()
            .data(domainRange, function (d) {
                return d;
            })
            .enter()
            .append('g')
            .attr('class', 'g-legend')
            .append('rect')
            .attr('x', function (d, i) {
                return legendWidth * i;
            })
            .attr('y', 20)
            .attr('rx', 0)
            .attr('ry', 0)
            .attr('width', legendWidth)
            .style('width', '' + legendWidth + 'px')
            .attr('height', 10)
            .style('height', '10px')
            .style('fill', function (d, i) {
                return '' + HeatMap_ColorRange[i];
            });

        legend
            .selectAll('.g-legend')
            .append('text')
            .attr('class', 'g-legend-text')
            .text(function (d) {
                return '' + d;
            })
            .attr('x', function (d, i) {
                if (i === 0) {
                    return 0;
                }
                return legendWidth * i;
            })
            .attr('y', 10)
            .style('font-size', 11)
            .style('fill', '#ADADAD');

        legend.exit().remove();
    }
}

Code is pretty much self explanatory, there are two scales and axis (x, y) and the data is represented with groups, variables and domain range (color schema).

Check out the complete Angular project code on GitHub repo.

Hope you like the post!.

In the next post I will be trying transitions (animations), responsiveness and tooltips for the bar chart, so be sure to check it out.

Developing charts using d3.js in Angular.

d3.js is one of the popular library to draw charts as per your requirements or needs. The library gives the developer with many options or freedom to draw the charts. Their gallery has many incredible examples and the community around this library is also quite impressive.

Its super easy to get started. First off you need some basic knowledge about SVG and CSS. There are many posts or articles out there for you to explore. But in this post I will be talking about using d3.js to develop a simple bar chart in Angular application.

Dependencies
  1. A sample angular project to get started or use @angular/cli to create one, again many posts available online to do this.
  2. Install D3 packages.
    1. $npm i d3
    2. $npm i -D @types/d3
Bar chart

Create a new bar chart component $ng g c BarChart. A bar chart typically has two axis (X, Y) and the bars.

import {
    Component,
    OnInit,
    Input,
    ChangeDetectionStrategy,
    ChangeDetectorRef,
    ViewChild,
    ElementRef,
} from '@angular/core';
import * as d3 from 'd3v6';
import cloneDeep from 'lodash/cloneDeep';
import { DataItemInterface } from '../commom/interfaces/data-item.interface';
import { ChartsDataService } from '../commom/services/charts-data.service';

@Component({
    selector: 'app-bar-chart',
    templateUrl: './bar-chart.component.html',
    styleUrls: ['./bar-chart.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BarChartComponent implements OnInit {
    @ViewChild('chartContainer', { static: true }) chartContainer: ElementRef;
    @Input() chartId: string = 'bar';
    @Input() width: number = 600;
    @Input() height: number = 450;
    _chartData: DataItemInterface[];

    private margin = { top: 20, right: 20, bottom: 35, left: 40 };
    private _chart = {
        svg: null,
        xAxis: null,
        yAxis: null,
        xScale: null,
        yScale: null,
        x: null,
        y: null,
        mainContainer: null,
        bars: null,
    };

    get barWidth(): number {
        {
            return this.width - this.margin.left - this.margin.right;
        }
    }

    get barHeight(): number {
        {
            return this.height - this.margin.top - this.margin.bottom;
        }
    }

    constructor(
        private _cd: ChangeDetectorRef,
        private dataService: ChartsDataService
    ) {
        this._chartData = dataService.getData(22);
        this._cd.markForCheck();
    }

    ngOnInit(): void {
        this.initChart();
        this.draw();
        this._cd.markForCheck();
    }

    @Input()
    set chartData(items: DataItemInterface[]) {
        if (items) {
            this._chartData = cloneDeep(items);
            this.draw();
        }
    }

    get chartData(): DataItemInterface[] {
        return this._chartData;
    }

    private initChart() {
        this._chart.svg = d3.select('#bar').select('svg');
        this._chart.xScale = d3.scaleBand();
        this._chart.yScale = d3.scaleLinear();
        this.setSVGDimensions();
        this._chart.mainContainer = this._chart.svg
            .append('g')
            .attr(
                'transform',
                `translate(${this.margin.left}, ${this.margin.top})`
            );
        this._chart.y = this._chart.mainContainer
            .append('g')
            .attr('class', 'axis axis--y');
        this._chart.x = this._chart.mainContainer
            .append('g')
            .attr('class', 'axis axis--x');
    }

    private setSVGDimensions() {
        const rect = this.chartContainer.nativeElement.getBoundingClientRect();
        this.width = rect.width;
        this._chart.svg.style('width', this.width).style('height', this.height);
    }

    private draw() {
        this.setAxisScales();
        this.drawAxis();
        this.drawBars();
    }

    private setAxisScales() {
        this._chart.xScale = d3.scaleBand();
        this._chart.yScale = d3.scaleLinear();

        this._chart.xScale
            .rangeRound([0, this.barWidth])
            .padding(0.1)
            .domain(this.chartData.map((d) => d.name));
        this._chart.yScale
            .range([this.barHeight, 0])
            .domain([0, Math.max(...this.chartData.map((x) => x.value))]);
        this._chart.xAxis = d3.axisBottom(this._chart.xScale);
        this._chart.yAxis = d3.axisLeft(this._chart.yScale);
    }

    private drawAxis() {
        this._chart.y
            .attr('transform', `translate(0, 0)`)
            .call(this._chart.yAxis);
        this._chart.x
            .attr('transform', `translate(0, ${this._chart.yScale(0)})`)
            .call(this._chart.xAxis);
    }

    private drawBars() {
        const calcBarY = (yPos) => {
            return !yPos ? this._chart.yScale(0) - 1 : this._chart.yScale(yPos);
        };
        const calcBarHeight = (yPos) =>
            Math.max(0, this._chart.yScale(0) - calcBarY(yPos));

        this._chart.bars = this._chart.mainContainer
            .selectAll('.bar')
            .remove()
            .exit()
            .data(this.chartData)
            .enter()
            .append('rect')
            .attr('class', 'chart-bar');

        this._chart.bars
            .attr('x', (d) => this._chart.xScale(d.name))
            .attr('y', (d) => this._chart.yScale(d.value))
            .attr('width', this._chart.xScale.bandwidth())
            .attr('height', (d) => calcBarHeight(d.value));
    }
}

The above code is basically self-explanatory, try changing things and explore, and see below the chart we get.

Let’s look at our bar chart template code.

<div class="bar-chart" #chartContainer>
    <div class="bar-chart__main" id="bar">
        <svg></svg>
    </div>
</div>

And finally some styling…

.bar-chart {
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;

    &__main {
        flex: 1;
        position: relative;

        ::ng-deep svg {
            $lineColor: #6f7583;
            outline: none;

            .axis {
                path,
                line {
                    fill: none;
                    stroke: $lineColor;
                    stroke-width: 0.5;
                    shape-rendering: crispEdges;
                }

                text {
                    fill: #c3c3c3;
                    stroke: #c3c3c3;
                    stroke-width: 0.5;
                }
            }

            .chart-bar {
                outline: none;
                stroke: transparent !important;
                fill-opacity: 1 !important;
            }
        }
    }
}

Check out the complete Angular project code on GitHub repo.

Conclusion

d3.js is a powerful library but there is also a steep learning curve and you could easily get lost when trying to debug or find some solution. Best recommend approach is to go over the docs first and understand how things work under the hood.

Hope you like the post!.

In the next post I will be trying transitions (animations), responsiveness and tooltips for the bar chart, so be sure to check it out.

Developed mobile application using Ionic and Angular.

Ionic framework lets you develop mobile applications with single code base for multiple platforms. It basically lets us build mobile experiences with open web.

This is something very cool.

So, I took some time out and developed one Android mobile application.

App – https://play.google.com/store/apps/details?id=com.ninetyninet.hogenakkal_falls

Check it out and please let me know.

Github code at – https://github.com/kumargandhi/hogenakkal-falls

Features
  • Browse through exotic collection of photos.
  • Google maps integration, showing location on map.
  • Social sharing.
  • AdMob integration.

Using forkJoin in Angular to perform multiple HTTP requests but wait until all done.

forkJoin is an operator in RxJS which takes one more observables as inputs and then wait until all observables to emit and complete.

One popular use case in Angular where we should use forkJoin is while performing multiple HTTP requests. There will be a use case where we would want to finish few HTTP requests in one go because the data fetched from one API call or HTTP request might be dependent on other API call data.

For example, say we are fetching all the users in an application, however we would also want to fetch all the user roles too so we can map the users data to the roles accordingly in the grid or make some decisions. So the solution for this is to use the forkJoin operator pass these two HTTP request as the inputs and wait for these two HTTP requests to complete, once done, we will have all the data we need in a single observable array so we can proceed to parse the data.

Lets, see some code example.

// Service class with two mock API functions to fetch the data.

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { IUser } from '../interfaces/user.interface';
import { MOCK_USER_ROLES, MOCK_USERS_DATA } from '../constants';

@Injectable({
    providedIn: 'root',
})
export class UserService {
    
    // Mock function to fetch the users data.
    getUsers(): Observable<IUser[]> {
        return of(MOCK_USERS_DATA).pipe(delay(2000));
    }

    // Mock function to fetch the user roles.
    getUserRoles(): Observable<string[]> {
        return of(MOCK_USER_ROLES).pipe(delay(2000));
    }
}
// Mock data for our service requests.

import { IUser } from './interfaces/user.interface';

// Mock users data
export const MOCK_USERS_DATA: IUser[] = [
    {
        id: '1',
        email: 'jackbing@gmail.com',
        roles: ['Student'],
        displayName: 'Jack Bing',
    },
    {
        id: '2',
        email: 'chandlerbing@gmail.com',
        roles: ['Super Admin'],
        displayName: 'Chandler Bing',
    },
    {
        id: '3',
        email: 'appAdmin@gmail.com',
        roles: ['Teacher'],
        displayName: 'Admin',
    },
];

// Mock user roles data
export const MOCK_USER_ROLES: string[] = ['Student', 'Super Admin', 'Teacher'];
// User interface, defines the type of our users data.

export interface IUser {
    id: string;
    email: string;
    password?: string;
    displayName: string;
    photoURL?: string;
    emailVerified?: boolean;
    roles: string[];
}
import { Component, OnInit } from '@angular/core';
import { UserService } from './services/user.service';
import { forkJoin } from 'rxjs';
import { IUser } from './interfaces/user.interface';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
    users: IUser[] | undefined;

    userRoles: string[] | undefined;

    constructor(private _userService: UserService) {}

    ngOnInit() {
        this.getUsersData();
    }

    // We are using the forkJoin operator for the two requests from the service class.
    getUsersData() {
        forkJoin({
            users: this._userService.getUsers(),
            userRoles: this._userService.getUserRoles(),
        }).subscribe(
            ({ users, userRoles }) => {
                this.users = users;
                this.userRoles = userRoles;
            },
            (error) => {
                console.log(error);
            }
        );
    }
}

Above code sample function getUsersData is where we are using the forkJoin to make multiple HTTP requests and then we process the response from all the requests once all the requests are completed!!.

How TypeScript changed my life.

Don’t get me wrong, JavaScript is a powerful language. However, it is also a dynamic language.

The problem with being dynamic language is you can do all kinds of crazy things, like, reference variables that don’t exist or work with objects of an unknown shape, since the code is executed by the browser if there are issues with your code you won’t be able to catch it until runtime when the browser throws an error and then your code / app is broken for good!!.

Runtime error and my app is broken!

Now, the whole point of TypeScript language is it prevents just that…prevents errors from ever happening by extending JavaScript with types. Hence the language is a super-set of JavaScript which means you can write plain JavaScript with all of its features completely optional.

IDE’s have become smarter now that they provide instant feedback when writing code because TypeScript behaves like a compiled language where as JavaScript is the compilation target.

Feedback by IDE

For example, IDE will provide feedback about using a variable that does not exist and we can fix this issue then and there instead of fixing this issue weeks or months later when a hardworking QA engineer finds it. And we could have saved lot of time and money, also most importantly not find yourself in an embarrassing situation.

So, TypeScript, the primary goal of this language is to enable static typing, one way to achieve that is by allowing you to annotate your code with types.

let userName: string = 'Kumar Gandhi';
// Also known as explicit type since we set the type. 

In above code sample you can see strongly typed variable using a colon followed by a type.

There are other types we can set, like, number, boolean …This is called explicit type. So if we try to assign a value of wrong type then we get an error. Alternatively, we can also set initial value and it will implicitly assign type, see below code.

let userName = 'Kumar Gandhi';
// Type is implicitly set to string since the value is of type string.

You can also opt-out of type when you annotate with the any type which allows you to loosely type or opt-out of type checking.

let userName: any;
// Opt-out of type check and type can change dynamically.

You can also define your own custom types and interfaces which is extremely powerful when working with objects.

// Custom type using interface
export interface User {
    id: string;
    email: string;
    displayName: string;
    photoURL?: string;
}

// My variable with custom type
let currentUser: User;

Above User interface defines various types of the properties on an object. We can then apply this interface to the plain JavaScript object, the currentUser object.

The beauty of having this strongly typed code is that we get auto-complete everywhere in our IDE and we don’t have to jump back and forth through documentation or traces to figure our why our code is not working!!.

Auto-complete by IDE
Working with TypeScript project
  • Project will will have a tsconfig.json file which provides ways to customise the behaviour of the compiler.
  • Run the TypeScript compiler using the tsc command it will take the .ts file and transpile it into vanilla JavaScript.
  • You may also choose which version of JavaScript you want to in the compiler settings if you are targeting some older browser.
  • This means you can start using the latest features of JavaScript without having to worry if they will be supported in an older environment.
Conclusion