Do users of your Angular app need to visualize their data? If so, consider using ngx-echarts.

And when you do, you too will produce charts that look like this:

 

And this:

 

If that's what you're looking for, read on.

Alternatively, you can go straight to the source on GItHub.

Remember: if you're having problems running the Angular app locally, be sure to check the README.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub code for each guide belongs to a specific branch. The master branch holds the entire application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Note that the website displays the guides in reverse chronological order so if you want to start from the beginning, go to the last page.

The Business Requirements

Your boss Smithers walks into your office clearly disappointed because his NCAA Men's College Basketball bracket is so busted.

He sits down and sighs. "It's about that CRM app you're working on," he says. "Upper management wants charts. We need to graph the data so users can quickly get a read on where they stand in terms of upcoming deals."

Smithers mutters something about Oral Roberts beating Ohio State. You don't quite make it out.

"Also, put those charts on the dashboard," he says. "That's a cool place to put that kind of stuff."

He gets up and walks out, asking himself why Winthrop couldn't beat Villanova.

Meet ngx-echarts

There are a few different options when it comes to libraries that will make it easy for you to create charts with your Angular app. I've decided to go with ngx-echarts.

Why? For starters, it's based on an Apache solution. Rarely do you go wrong when you choose Apache.

Secondly, I've used it and it works extremely well. It's also thoroughly documented and very flexible.

So I recommend you go with it as well. 

Here's how to get the ball rolling with ngx-echarts. Head over to your command prompt and go to the root directory of your project source.

Then, enter this command:

npm install echarts -S

Once that's done, enter this command:

npm install ngx-echarts -S

And, finally, enter this command:

npm install resize-observer-polyfill -D

Once you've got that done, you've installed ECharts, the Angular directive for ECharts, and the related depedency (that's the polyfill thing).

But you're still not ready to create charts.

Next, you need to update the imports for any modules that will display charts and graphs.

The code will look something like this:

import { NgxEchartsModule } from 'ngx-echarts';

@NgModule({
  imports: [
    ...,
    NgxEchartsModule.forRoot({
      echarts: () => import('echarts')
    })
  ],
})
export class AppModule { }

Please note: in that example, the import goes in AppModule.

However, that might not be the case with your project. You'll need to import it into whatever module draws the charts.

In this CRM application, that import goes into DealsModule.

You've Got Options

When it comes to drawing charts with your Angular app, you need to remember one word: options.

That's the name of the object you'll use to set all your chart properties.

And I mean all your chart properties.

That options object for drawing the first bar graph you saw in the intro looks like this:

this.options = {
  grid: {
    //prevents cutoff of y-axis labels
    left: '15%'
  },
  xAxis: {
    type: 'category',
    data: Array.from(this.map.keys())
  },
  yAxis: {
    type: 'value',
    axisLabel: {
      formatter: function (value) {
        return '$' + (value / 1000) + 'K';
      }
    }
  },
  series: [{
    data: Array.from(this.map.values()),
    type: 'bar'
  }]
};

Don't worry if you don't understand that all right away. I'll explain it later.

For now, just understand that options specifies the content and style of your chart.

Then, on the HTML side, you'll reference the options object as follows:

<div echarts [options]="options"></div>

That's it. That's all you need to do.

So, as you can see, all the heavy lifting happens in the component class. As it should be.

Drawing a Bar Graph

I won't cover how to draw all the charts in the intro. Instead, I'll just explain how to draw that first bar graph labeled "Upcoming Deal Closures."

I'll cover the rest in separate articles.

Before I get started explaining the graph, though, I need to explain the data.

The data here concerns upcoming deals. Each deal has an expected closure date.

The point here is to show the values for deals expected to close during the next 60 days. That will give sales reps an  idea of how well their pipelines are performing.

If two deals close on the same day, then their values get added together and the sum gets reported on the chart.

For the purposes of this guide, you're going to draw a bar chart with the following dates and values:

  • 4/22/21: $600,000
  • 4/29/21: $400,000
  • 5/5/21: $20,000
  • 5/12/21: $600,000
  • 5/17/21: $50,000

And you know what the chart is supposed to look like because you've already seen it.

Now it's time to draw that thing.

Put That Thing Where It Belongs

And now a question: where should you put the component that draws the chart?

Well, from the previous section you know that the chart displays deal info. So that sounds like it belongs in the deals module.

And that is indeed where you should put it.

Go back to that command line and enter this:

ng g c features/deals/charts/future-pipeline

That's going to give you the skeleton of a new component that belongs to the deals module.

But you also need to update the module itself.

Add the FuturePipelineComponent to the exports section of the module.

  exports: [
    DealsByContactComponent,
    FuturePipelineComponent
  ],

Why? Go back to what Smithers said.

He wants you to put the charts on the dashboard. That's a separate module.

So you'll need to export the component from DealsModule and then import DealsModule in DashboardModule:

@NgModule({
  declarations: [
    DashboardComponent
  ],
  imports: [
    CommonModule,
    DealsModule,
    FlexLayoutModule,
    MatProgressSpinnerModule,
    MatCardModule,
    RouterModule.forChild(routes)
  ]
})
export class DashboardModule {
    constructor() { }
}

Then you'll have access to the component you just created.

That's how you'll maintain a clean separation of concerns and keep everything nice and neat.

Charting a Course

Now it's time to grab the data and put it in that options object that you saw earlier. Do that in the FuturePipelineComponent class.

Here's what that class should look like:

const maxResults: number = 6;
const maxDays: number = 60;

@Component({
  selector: 'app-future-pipeline',
  templateUrl: './future-pipeline.component.html',
  styleUrls: ['./future-pipeline.component.css']
})
export class FuturePipelineComponent implements OnInit {

  options: any;
  map: Map<string, number> = new Map<string, number>();
  pageTitle: string = 'Upcoming Deal Closures';
  pageSubtitle: string = 'Next ' + maxDays + ' Days';

  constructor(private dealService: DealService, private userService: UserService,
    private dateService: DateService) { }

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

  private getDealCriteria(): DealCriteria {
    let criteria: DealCriteria = new DealCriteria();
    criteria.userId = "6014081e221e1b534a8aa432";
    criteria.orderType = "ASC";
    criteria.maxResults = maxResults;
    criteria.minDate = Date.now();
    criteria.maxDate = this.dateService.getDaysForwardAsNumber(maxDays);

    return criteria;
  }

  private loadDeals() {
    let criteria: DealCriteria = this.getDealCriteria();

    this.dealService.fetchDealsByCriteria(criteria).subscribe(
      (deals: Deal[]) => this.handleDealsResponse(deals),
      (err: Error) => this.handleError(err)
    );
  }

  private handleDealsResponse(deals: Deal[]) {
    console.log(deals);
    deals.forEach(deal => {
      this.addDealValue(deal);
    });

    console.log(this.map);

    this.setOptions();
  }

  private addDealValue(deal: Deal) {
    let date: string = this.dateService.getShortDateDisplay(deal.expectedClosureDate);
    let value: number = this.dealService.getAmount(deal);

    if (this.map.get(date)) {
      let currentVal: number = this.map.get(date);
      let newVal: number = currentVal + value;

      this.map.set(date, newVal);
    } else {
      this.map.set(date, value);
    }
  }

  private setOptions() {
this.options = {
  grid: {
    //prevents cutoff of y-axis labels
    left: '15%'
  },
  xAxis: {
    type: 'category',
    data: Array.from(this.map.keys())
  },
  yAxis: {
    type: 'value',
    axisLabel: {
      formatter: function (value) {
        return '$' + (value / 1000) + 'K';
      }
    }
  },
  series: [{
    data: Array.from(this.map.values()),
    type: 'bar'
  }]
};
  }

  private handleError(err: Error) {
    console.error(err);
  }
}

There's a lot going on there so I'll break it down method-by-method.

The ngOnInit() method just handles one responsibility: loading the deals from the back-end database. It does that with the aid of a service class that in turns makes a call to the microservice that handles CRM-related tasks.

I won't cover the responsibilities of the microservice here because that's outside the scope of this article.

That loadDeals() method invoked from ngOnInit() handles the service call in fairly boilerplate fashion. It's an asynchronous call to the downstream application so it subscribes to an Observable that ultimately returns an array of Deal objects.

Note though that loadDeals() uses a DealCriteria object to set the criteria that gets passed to the downstream service.

I think that most of what's going on in getDealCriteria() is fairly self-explanatory. For testing purposes, I've hardcoded the user ID whereas I'd normally get the ID from a cached User object in UserService.

If there's any kind of problem making the call to the downstream service, execution goes to handleError(). Otherwise, it goes to handleDealsResponse().

And that handleDealsResponse() method is where the fun begins.

Since you don't need all the information from each deal, you need a method that will extract only what you do need.

Remember: all you need is the value of each deal and the date that it's expected to close. But you'll get a lot more than that in each Deal object.

That's why the addDealValue() method exists. For each Deal object, that method puts the relevant info into a Map object that simply associates the date with its respective deal value.

Note that addDealValue() checks for duplicate dates and, if it finds any, it will add the value of the deal it's currently looking at to the existing value that's associated with the same date.

So in that Map object, the key is the date and the value is the deal value.

Once the Map object is created, the code moves on to setOptions(). Take a look at what's going on there:

this.options = {
  grid: {
    //prevents cutoff of y-axis labels
    left: '15%'
  },
  xAxis: {
    type: 'category',
    data: Array.from(this.map.keys())
  },
  yAxis: {
    type: 'value',
    axisLabel: {
      formatter: function (value) {
        return '$' + (value / 1000) + 'K';
      }
    }
  },
  series: [{
    data: Array.from(this.map.values()),
    type: 'bar'
  }]
};

You've already seen that code but this time I'm going to explain it.

For starters, feel free to check out the entire ECharts documentation on the Apache website. You can get more detail about any of those properties over there.

The grid property specifies details about the grid that ECharts puts on the screen. In this case, the code just moves the whole chart a little to the left so as to prevent y-axis label cutoffs.

The xAxis property specifies details about the x-axis. 

For the type property, the code uses 'category' because it's treating the date string here as a category rather than a point in time.

You'll probably find that you use 'category' most of the time for type on the x-axis.

For xAxis data, the code is translating all the keys in the Map object to an array. That will be an array of string objects where each string will represent a different date.

For yAxis, the code sets type to 'value'. That's telling ECharts that you have a bunch of numbers on the y-axis.

Now the plot thickens with axisLabel. What the heck is going on there?

Well, you don't want to display the value as 600000 because that looks fugly.

First of all, you want a dollar sign in front of it. Second of all, that's way to long.

So the axisLabel property uses a formatter to make the y-axis labels look pretty. In this case, 600000 becomes $600K.

Finally, take a look at the series property.

First, that data property sets the data points on the y-axis. It does that by taking the values from the Map object and converting them to an array. Just like you saw earlier under xAxis.

Next, the type property here specifies the type of chart to display. It's set to 'bar' for bar chart.

Charter Member

Now that the component is done, it's time to move on to the markup.

Edit future-pipeline-component.html and make it look like this:

<div fxLayout="column" fxFlexFill>
  <mat-card class="chart">
    <div fxLayoutAlign="center center" *ngIf="!options" class="chart-spinner">
      <div><mat-spinner [diameter]="50"></mat-spinner></div>
    </div>
    <div fxLayout="column" fxLayoutGap="20px" *ngIf="options">
      <div fxFlex="100" class="chart-title">{{pageTitle}}</div>
      <div fxFlex="100" class="chart-subtitle">{{pageSubtitle}}</div>
      <div echarts [options]="options"></div>
    </div>
  </mat-card>
</div>

There's not a whole lot going on there but that's by design. As I mentioned before, most of the hard work is handled by the component class.

I won't get into the Angular Flex Layout API here either as that's beyond the scope of this guide. I did cover it quite a bit in the guide on creating a responsive form.

To make things pretty, the code puts the chart in an Angular Material card. And it shows a spinner until the deals are retrieved from the back end.

Once the deals are retrieved and the options set, the code above shows the chart with single line I showed you earlier:

<div echarts [options]="options"></div>

That's it. Save that file.

Now to make Smithers happy you can add it to the dashboard as follows:

<div fxFlex.gt-md="49" fxFlex="100"><app-future-pipeline></app-future-pipeline></div>

If only everything in life were that easy.

Wrapping It Up

There you have it. Now you know how to use ngx-echarts to put some awesome charts in your Angular UI.

But all you learned here is how to do a bar graph. That means you need to tinker.

Create a pie chart or line graph. Try a stacked bar graph. 

The possibilities are endless.

Of course, you can also feel free to review the code on GitHub.

Have fun!

Photo by Lorenzo from Pexels