Interactive Angular 2 Charts with ZingChart

Building gorgeous and interactive Angular 2 charts is easy with ZingChart

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

This is an updated version of our previous ZingChart and Angular 2 blog post

Since we last published a blog post about Angular 2 and ZingChart, there have been some considerable changes to the library. Consequently, the demos we provided will work for the Angular 2 beta 9 but will break in newer versions. The following example was written for Angular 2 RC4.

What's New in Angular 2 RC4

Since we published our last blog post, Angular 2 has moved from 2.0.0 beta versions to 2.0.0 release candidates (RC). What that means is the code we're working with has the potential to be the released version of Angular 2. Here's what's new since our last blog post...

Angular 2 now resides under the @angular2 scope

// 2.0.0 beta 9 -------------------------
import { XXXX } from 'angular2/...'

// 2.0.0 RC4 ----------------------------
import { XXXX } from '@angular/...'

Location of the bootstrap symbol has changed

// 2.0.0 beta 9 -------------------------
import { bootstrap } from 'angular2/platform/browser'

// 2.0.0 RC4 ----------------------------
import { bootstrap } from '@angular/platform-browser'

Location of the Component symbol has changed

// 2.0.0 beta 9 -------------------------
import { Component } from 'angular2/core'

// 2.0.0 RC4 ----------------------------
import { Component } from '@angular/core'

Location of the AfterViewInitsymbol

/// 2.0.0 beta 9 -------------------------
import { AfterViewInit } from 'angular2/core'

// 2.0.0 RC4 ----------------------------
import { AfterViewInit } from '@angular/core'

#item of items is now let item of items

/// 2.0.0 beta 9 -------------------------
template: '<div *ngFor="#item of items"></div>'

// 2.0.0 RC4 ----------------------------
template: '<div *ngFor="let item of items"></div>'

These are the main changes that affect our demo. Read the full changelog here.

Creating a Demo

Now that we've covered what's changed, it's time to create a new demo. Here's what we'll be making.

Let's get started!

1) Create our index.html file

Here, we're loading all the necessary files to get our Angular 2 demo up and running.

<!DOCTYPE html>
<html>
  <head>
    <title>ZingChart - Angular 2 RC4</title>
    <meta charset="UTF-8">
    <script src="https://npmcdn.com/core-js/client/shim.min.js"></script>
    <script src="https://npmcdn.com/zone.js@0.6.12?main=browser"></script>
    <script src="https://npmcdn.com/reflect-metadata@0.1.3"></script>
    <script src="https://npmcdn.com/systemjs@0.19.27/dist/system.src.js"></script>
    <script src="https://cdn.zingchart.com/zingchart.min.js"></script>
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

That <my-app></my-app element is where our app will be injected. Pretty cool!

2) Create our systemjs.config.js file

This file is where we specify the names of the files we want to load and where they exist. It allows us to do nifty import statements without having to load every script in the head. For now, it's just a bunch of boilerplate. We don't need to change anything to get it working.

We're using the Angular2 Quickstart SystemJS config file. It may be a bit bloated for such a simple demo but it gives us a nice starting point if we decide to add on later. You can view it here or copy the code below.

/**
 * PLUNKER VERSION (based on systemjs.config.js in angular.io)
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function(global) {

  var ngVer = '@2.0.0-rc.4'; // lock in the angular package version; do not let it float to current!
  var routerVer = '@3.0.0-beta.1'; // lock router version
  var formsVer = '@0.2.0'; // lock forms version
  var routerDeprecatedVer = '@2.0.0-rc.2'; // temporarily until we update all the guides

  //map tells the System loader where to look for things
  var  map = {
    'app':                        'app',

    '@angular':                   'https://npmcdn.com/@angular', // sufficient if we didn't pin the version
    '@angular/router':            'https://npmcdn.com/@angular/router' + routerVer,
    '@angular/forms':             'https://npmcdn.com/@angular/forms' + formsVer,
    '@angular/router-deprecated': 'https://npmcdn.com/@angular/router-deprecated' + routerDeprecatedVer,
    'angular2-in-memory-web-api': 'https://npmcdn.com/angular2-in-memory-web-api', // get latest
    'rxjs':                       'https://npmcdn.com/rxjs@5.0.0-beta.6',
    'ts':                         'https://npmcdn.com/plugin-typescript@4.0.10/lib/plugin.js',
    'typescript':                 'https://npmcdn.com/typescript@1.9.0-dev.20160409/lib/typescript.js',
 };

  //packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                        { main: 'main.ts',  defaultExtension: 'ts' },
    'rxjs':                       { defaultExtension: 'js' },
  };

  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'upgrade',
  ];

  // Add map entries for each angular package
  // only because we're pinning the version with `ngVer`.
  ngPackageNames.forEach(function(pkgName) {
    map['@angular/'+pkgName] = 'https://npmcdn.com/@angular/' + pkgName + ngVer;
  });

  // Add package entries for angular packages
  ngPackageNames.forEach(function(pkgName) {

    // Bundled (~40 requests):
    packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };

    // Individual files (~300 requests):
    //packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
  });

  // No umd for router yet
  packages['@angular/router'] = { main: 'index.js', defaultExtension: 'js' };

  // Forms not on rc yet
  packages['@angular/forms'] = { main: 'index.js', defaultExtension: 'js' };

  // Temporarily until we update the guides
  packages['@angular/router-deprecated'] = { main: '/bundles/router-deprecated' + '.umd.js', defaultExtension: 'js' };

  var config = {
    // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      tsconfig: true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },
    map: map,
    packages: packages
  };

  System.config(config);

})(this);

3) Create our tsconfig.json file

This is where we specify how we want TypeScript to compile our .ts files. Yet again, we're copying it straight from the Angular 2 Quickstart example.

It specifies that we want our TypeScript to compile to ES5 for compatibility and use the CommonJS format for module loading.

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true
  }
}

4) Create our app directory

Everything from here on out will be done inside the app directory. Let's make that directory now.

mkdir app && cd app

5) Create our Chart model files

Now, we're going to make the model for all of our charts. This is where we specify the structure of the data we're going to be using. It allows us to enforce convention of what's expected and set some nice defaults.

export class Chart {
  id: String;
  data: Object;
  height: any;
  width: any;

  constructor (config: Object) {
    this.id = config.id;
    this.data = config.data;
    this.height = config.height || 400;
    this.width = config.width || '100%';
  }
}

We're using ES6/ES2015 classes to make a Chart class with the following attributes: id, data, height, and width. We use TypeScript to declare what types we expect them to be.

  • id is a string that refers to the DOM node at which we'll be rendering our chart.
  • data is the object that configures what our chart looks like, what data it's rendering, etc.
  • height and width can be either strings ("100%") or numbers (300). As such, we use type any for them.

The constructor is called when creating a new Chart instance. In that, we accept a config object and map the properties of the config to the instance. We declare that height should be 400 if no height attribute exists in the config, and width should be "100%" if no width attribute exists in the config. Otherwise, default to what the config has set.

Our Chart class is exported at the top of the file (*export* class Chart). That allows us to use it inside other files WHICH IS WHAT WE'RE ABOUT TO DO! Get ready for it!

Congrats, you're getting there! Props for sticking with it. Now, we're going to get into the dopest part of this lesson: Angular 2.

6) Create our ZingChart Component

Time to dig into Angular 2 components! Let's make a file inside our app directory called zingchart.component.ts. This is where we'll create the actual ZingChart component that we'll use in our app. Pretty cool.

Here's what the code looks like...

import { Component, NgZone, AfterViewInit, OnDestroy } from '@angular/core';
import { Chart } from './chart.model';

@Component({
  selector: 'zingchart',
  inputs: ['chart'],
  template: '<div id="{{chart.id}}"></div>'
})
export class ZingChart implements AfterViewInit, OnDestroy {  
  chart: Chart

  constructor (private zone: NgZone) { }

  ngAfterViewInit () {
    this.zone.runOutsideAngular(() => zingchart.render(this.chart));
  }

  ngOnDestroy () {
    zingchart.exec(this.chart.id, 'destroy');
  }
}

Now we're cooking with gas! We've got quite a lot going on here. Let's break it step by step...

Load the necessary Angular 2 elements

import { Component, NgZone, AfterViewInit, OnDestroy } from '@angular/core';

This is where we load the main Angular 2 magic we'll need. Component lets us make reusable elements that function like normal HTML but are much more powerful. NgZone is used to work with the powerful ZingChart library and allow it to render efficiently inside of Angular 2. AfterViewInit allows us to do work just after the view initializes. Finally, OnDestroy allows us to do work just before the view is destroyed. Pretty cool.

Load the Chart model

import { Chart } from './chart.model`;

Hey, that looks familiar! Remember the Chart class we made in Step 5? The one with the nifty export? Now we're using it!

As previously mentioned, the export statement allows us to import that functionality later. That's exactly what we're doing here: importing our Chart model.

Create the Component

@Component({
  selector: 'zingchart',
  input: ['chart'],
  template: '<div id="{{ chart.id }}"></div>'
})

Remember the first line of the file where we imported a bunch of stuff from @angular/core? The @Component was one of them! The @Component() is one of the newest parts of Angular 2. It's called a decorator, and Angular 2 has several different build-in decorators. They "allow us to add metadata to a class, its members, and function arguments." It must come directly above a Class declaration.

So what's going on in our @Component() decorator?

  • selector lets us specify a custom HTML tag name. We're calling ours "zingchart" which allows us to make <zingchart></zingchart> elements later on. Pretty nifty!
  • input lets us specify what parent element attributes we want access to. This will become more clear in the next step, I promise.
  • template lets us declare what HTML we want inside our custom <zingchart></zingchart> element. Ours is super simple: it's just a <div> with the ID we specify in our model. The double curly brackets ({{ }}) let us inject stuff into our template. We're injecting the chart ID into the template.

Create and export the ZingChart Class

export class ZingChart implements AfterViewInit, OnDestroy {  
  chart: Chart

  constructor (private zone: NgZone) { }

  ngAfterViewInit () {
    this.zone.runOutsideAngular(() => zingchart.render(this.chart));
  }

  ngOnDestroy () {
    zingchart.exec(this.chart.id, 'destroy');
  }
}

What have we here? First off, we're creating (and exporting) a ZingChart Class. Just like how we created and exported the Chart class earlier, this will allow us to load the ZingChart class later in our code. We're also stating that our ZingChart class implements the OnInit and OnDestroy interfaces we loaded at the top of the file. Implementing is something new to TypeScript.

We're declaring that our ZingChart class has an attribute called chart and it's of type Chart (which we imported at the top of the file).

Inside the constructor, we're creating a private variable called zone that is of type NgZone (which we imported at the top of the file).

The AfterViewInit and OnDestroy interfaces allow us to tie into the lifecycle of the component. AfterViewInit let us create a function, ngAfterViewInit, which Angular 2 calls automatically right after the component's view is initialized. OnDestroy lets us a create a function, ngOnDestroy, which Angular 2 calls right before the component's view is destroyed. We rely on those functions to both render the chart inside ngAfterViewInit and destroy the chart inside ngOnDestroy.

We rely on our zone inside ngAfterViewInit to execute our render function outside Angular 2's zone. Our previous blog post contains a rather succinct explanation:

ZingChart relies on window events such as window.setTimeout and window.setInterval for updating visual changes such as tooltips or highlighted state on the chart. By default, Zone.js will listen of these events as well and treat them as part of the applications lifecycle -- not good! We do not want the small visual changes that ZingChart provides to be listened to by Angular 2. Therefore, we need to render each chart outside of the zone.

Rendering the chart outside the zone allows the rest of the application to continue running speedily.

Whew, that was a lot! Angular 2 is powerful in that way. You can accomplish quite a bit without a ton of code. Time to move on...

7) Create our App Component

Here we go... Inside the app directory, create a file called app.component.ts. That's gonna be where we create the <my-app></my-app> element you saw inside our index.html file. Let's toss this inside it...

import { Component } from '@angular/core';

import { ZingChart } from './zingchart.component';
import { Chart } from './chart.model';

@Component({
  selector: 'my-app',
  template: '<zingchart *ngFor="let chart of charts" [chart]="chart"></zingchart>',
  directives: [ZingChart]
})
export class AppComponent {
  charts: Chart[];

  constructor () {
    this.charts = [
      {
        id: 'chart-1',
        data: {
          type: 'bar',
          series: [{
            values: [2, 3, 4, 5, 3, 3, 2]
          }]
        },
        height: 300
      }
    ]
  }
}

What's going on here? It's all gonna start to look similar.

Import the Component decorator

import { Component } from '@angular/core';

Just as we did in the zingchart.component.ts file, we load the Component decorator since we're making a component.

Import the ZingChart component

import { ZingChart } from './zingchart.component`;

Remember that zingchart.component.ts file we just made? Time to load it and finally use it.

Import the Chart model

import { Chart } from './chart.model';

Yet again, we're loading the Chart model we created so long ago.

Create the Component

@Component({
  selector: 'my-app',
  template: '<zingchart *ngFor="let chart of charts" [chart]="chart"></zingchart>',
  directives: [ZingChart]
})

Just like in our ZingChart component, we're making a new component with the HTML tag <my-app></my-app> (which you'll see inside the index.html file). That component has a <zingchart></zingchart> tag inside it (which we created in the zingchart.component.ts file. We're using the *ngFor directive to iterate over every Chart object in the element's charts array, making a new <zingchart></zingchart> element for each item of the array. We're then binding the Chart object of that iteration to the chart keyword.

Hold up. Let's shoot back to the zingchart.component.ts @Component decorator real quick. It has an attribute called inputs: inputs: ['chart']. That 'chart' inside the inputs array corresponds to the [chart]="chart" in the app.component.ts template, allowing us to access the parent data (the app.component.ts chart object) inside the child component (zingchart.component.ts). Nifty.

Lastly, directives is where we specify what components we need. Since we're using <zingchart></zingchart> inside the template, we need to tell our component that we need to load that component. Without the directives: [ZingChart] declaration, we'd just get one boring invalid HTML tag called <zingchart> with nothing inside instead of a gorgeous, interactive chart. That would be sad.

Create and export the AppComponent Class

export class AppComponent {  
  charts: Chart[];

  constructor () {
    this.charts = [
      {
        id: 'chart-1',
        data: {
          type: 'bar',
          series: [{
            values: [2, 3, 4, 5, 3, 3, 2]
          }]
        },
        height: 300
      }
    ]
  }
}

This is getting really similar now. We again create and export a Class, this time called AppComponent, which includes an attribute called charts that is an array of Charts. Remember the *ngFor="let chart of charts" bit from the template above? The charts part of that is referencing this array. Cool!

We then create a constructor (again), and set the charts array to include one object of type Chart. This part is pure ZingChart syntax. Visit the ZingChart docs to learn more.

That's it for app.component.ts! Not too shabby. We are ALMOST done. One more file!

8) Create the main script

Now it's time to tie it all together. Inside the app directory, create one last file called main.ts. Toss this inside.

import { bootstrap } from '@angular/platform-browser-dynamic';

import { AppComponent } from './app.component';

bootstrap(AppComponent);

So much smaller than usual! What a treat! Let's go over it real quick...

Import the bootstrap function

import { bootstrap } from '@angular/platform-browser-dynamic`;

We use this to "bootstrap" (aka "start") our application.

Import the AppComponent

import { AppComponent } from './app.component`;

Remember that super cool app.component.ts file we made earlier? Now, we're finally using it! Awesome!

Finally, call bootstrap with the AppComponent

bootstrap(AppComponent);

The bootstrap function starts up the app with the imported AppComponent. That's it! Our app is ready to go!

9) Start a web server and visit the URL!

Just start up a simple web server and visit the URL to get see all your hard work pay off. If you've got Python on your system, navigate to the main directory of the project (the one above app) and run the following...

python -m SimpleHTTPServer

That'll start a server at localhost:8000. Go there now and you should see a gorgeous bar chart! Yay!!! Congrats, amigo/amiga. You did it.

Recap

  • We covered what's changed from our previous demo of Angular 2.0.0 beta 9 to Angular 2.0.0 RC4.
  • We covered the boilerplate code to get a nice modular Angular 2 app started
  • We then created a custom app with a ZingChart component to make some beautiful interactive charts in Angular 2

Pat yourself on the back. Today was a good day.

Where to go from here

Our demo was based off the existing Angular 2 Quickstart demo on the Angular 2 site. If you're looking to build up from here, try running through the Angular 2: Tour of Heroes tutorial.

Keep an eye on our blog. We'll continue to release new Angular 2 content and showcase more ways you can build some awesome web apps.

Let us know how you use ZingChart and Angular 2 for your charting! Tweet at us at @ZingChart or shoot us an email at support@zingchart.com. We're always happy to chat.

ORIGINAL ARTICLE BY: Patrick RoDee of ZingChart