ZingChart & Angular 2 Charts: Back at it Again
This is an updated version of our previous ZingChart and Angular 2 blog post
This Angular blog post is deprecated. Please go to the Angular blog post linked below.
We've re-released an updated post for Angular 2.0.0 RC5. Read it here
Since we last published a blog post about Angular 2 Charts 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 With Angular 2 Charts in 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 AfterViewInit
symbol
/// 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
andwidth
can be either strings ("100%"
) or numbers (300
). As such, we use typeany
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
andwindow.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 Angular 2 charts.
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.