Angular 2 Charts With RC5
tl;dr
Want to just see the code? Check out the GitHub repo
Prefer to just read a diff? We've got that, too
This is an update of a previous blog post
Well well well, here we are again and Angular 2 is now at release candidate 5! What does that mean for you? Let's go over that now.
What's New in Angular RC5?
While the changes between RC4 and RC5 are not as extensive as the changes outlined in our last blog post, there are still some quite important changes to be aware of.
Modules are finally here!
Angular 1 was built around the idea of modules. Modules are self-contained pieces that can combine different functionality into one loadable and reusable container. In Angular 1, modules were created the following way...
var myModule = angular.module('myModule', []);
Until RC5, Angular 2 had no explicit way of creating a module. Now, we have the fandangled @NgModule
decorator.
Here's an example of how you'd create a module in Angular 2 RC5:
import { NgModule } from '@angular/core';
@NgModule({
declarations: [],
imports: [],
providers: [],
bootstrap: []
})
export class AppModule { }
It's all quite similar to creating a component in Angular 2. We have a decorator with metadata and an exported class directly underneath it. But, modules provide some nice features that save you dev time and help prevent future headaches.
Firstly, if you've tried to build anything in Angular 2 with any reusable directives or components, you're likely to have encountered the need to import
a component, directive, or service into every file that needs it.
That means that if you had three component files (user-view.component.ts
, user-list.component.ts
, and user-create.component.ts
, for example) and each had to use a service called user.service.ts
, you'd have to import the user.service.ts
in every one of those files and add it to the providers
for each component.
If you made a new component that relied on that service, then guess what? You gotta import the service in the new file, too, and set the provider
. Eww.
The end result is a lot of repeated code. Imagine each component had even more services and relied on additional components. You'd have to keep track of which dependencies each component had, updating the imports every time you wanted to use something new and removing imports if you no longer relied on it. That's a lot of overhead!
Well, modules save that headache. With modules, you can safely remove any service, directive, pipe, or component imports
from your component files. You can even remove the components
array, providers
array, pipes
array, and directives
array. All those can now go in the module!
Here's where each piece goes in the module...
@NgModule({
declarations: [/* Components, Pipes, and Directives go here */],
imports: [/* Modules go here */],
providers: [/* Services go here */],
bootstrap: [/* The main component goes here */]
})
With all pipes, directives, components, and services now loaded in @NgModule
, we can now use any of the loaded elements inside any component that we load into the module.
The end result is much less repeated code and much cleaner components.
Bootstrapping Modules
In RC4, we imported our bootstrap
function, the app.component
, and called bootstrap
with the app.component
as the argument. Since we now us @NgModule
, our app bootstrapping has also changed.
We now import the compilation method (a.k.a. how Angular 2 turns all the cool code and markup into actual JS and HTML), the app module, and fire it all up.
Here's what that looks like...
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
What Else?
Angular 2 RC5 added several new features to the library. While we won't cover them here, you can read all about them on the Angular 2 release notes.
Let's See Some Angular 2 Charts!
Here's a complete demo using Angular 2 with ZingChart.
Check out the code on GitHub or follow along below.
index.html
<!DOCTYPE html>
<html>
<head>
<title>ZingChart - Angular 2 RC5</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>
Here we're loading all our dependencies for Angular 2 and SystemJS, the ZingChart library, and our SystemJS config file.
systemjs.config.js
/**
* 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.5'; // lock in the angular package version; do not let it float to current!
var routerVer = '@3.0.0-rc.1'; // lock router version
var formsVer = '@0.3.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' },
'angular2-in-memory-web-api': { main: 'index.js', 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.concat(['forms', 'router', 'router-deprecated']).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' };
});
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);
In systemjs.config.js
, we're setting up how we want SystemJS to load our modules and code. This is exactly the same as the systemjs.config.js
example in the Angular 2 quickstart demo.
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}
Our tsconfig.json
file is the exact same as our tsconfig.json
demo provided in the Angular 2 quickstart demo. It just contains the way we want our Typescript to be compiled to JavaScript.
app/chart.model.ts
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%';
}
}
Our first piece of app code, chart.model.ts
is where we specify the data structure we want for our chart object. This structure exactly mirrors the structure that ZingChart accepts for rendering.
-
id
specifies the ID of the element where we'll render the chart -
data
specifies the actual chart data we'll be rendering -
height
specifies the height, defaulting to 400px -
width
specifies the width, defaulting to 100%
That's all we need for our chart model! Nice and simple.
app/zingchart.component.ts
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');
}
}
Again, nothing has changed here. We import Component
, NgZone
, AfterViewInit
, and OnDestroy
from the Angular 2 core. We import the Chart
from the chart model we made above. We then create a <zingchart>
component that accepts a chart as data input and renders a <div>
with the id
we give it.
That component hooks into the ngAfterViewInit
and ngOnDestroy
events to do the actual rendering and destroying of the chart. We declare a chart
on the component and a private NgZone
.
Using the ngAfterViewInit
event, we render the chart outside the component's zone. This bit might seem a little strange but there's a performance reason behind it. ZingChart uses setTimeout
and setInterval
to handle different parts of chart painting. Angular 2's zones hook into those functions by default.
Since ZingChart's use of setTimeout
and setInterval
is isolated to just the ZingChart library, we don't want Angular 2 to be snooping on them and assuming other UI components need to be updated. Calling zingchart.render
outside the Angular zone prevents that problem.
With the ngOnDestroy
event, we're listening to when Angular destroys the component. We hook into that event and destroy the chart just before it. Doing so ensures no memory leaks occur which is niiiiiiice.
That's all there is to the ZingChart component. The rest is just using it and setting up our app for Angular 2 RC5.
app/app.component.ts
import { Component } from '@angular/core';
import { Chart } from './chart.model';
@Component({
selector: 'my-app',
template: `<zingchart *ngFor="let chart of charts" [chart]="chart"></zingchart>`
})
export class AppComponent {
charts: Chart[];
constructor () {
this.charts = [
{
id: 'chart-1',
data: {
type: 'bar',
series: [
{
values: [1, 2, 3]
}
]
}
}
]
}
}
Aight, so what's going on here? As you can see from our git diff, all we did here was remove code. Well that's cool! Less code but the same functionality is a decent win.
First off, we removed the importing of the ZingChart component we created in app/zingchart.component.ts
. We're gonna load that in our NgModule
instead. DOPE.
Lastly, since we removed the ZingChart component import, we can safely remove the directives
from the component since we'll load it in the module. Fresh all day.
Overall, what we're doing is:
-
Importing the
Component
decorator from the Angular 2 core. -
Importing the
Chart
model we made inapp/chart.model.ts
-
Creating a component with the selector
<my-app>
-
Iterate over each
chart
object and make a<zingchart>
component with the data -
We then export the
AppComponent
class which contains an array ofChart
objects. -
We initialize the
charts
array with all theChart
objects we want rendered.
That's it! Not bad at all.
app/app.module.ts
Time to use that new @NgModule
business we discussed earlier. It's nifty.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ZingChart } from './zingchart.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, ZingChart ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Looks pretty similar to everything else, right? Here's what's going on...
-
We import the
NgModule
decorator from the Angular 2 core. -
We import the
BrowserModule
from the Angular 2 browser platform. -
We import the
AppComponent
from theapp/app.component.ts
file. -
We import the
ZingChart
component from theapp/zingchart.component.ts
file. -
We declare our module metadata.
We place BrowserModule
inside imports
since we're rendering in the browser. Since we use both the AppComponent
and ZingChart
components, we place them inside declarations
. Finally, we want to start with the AppComponent
so we place it inside bootstrap
.
All that's left to do is export the AppModule
. Tada! Our first module. Well done! Now, let's use that module.
app/main.ts
Like before, this is short and sweet. If you look at the git diff, you can all we made were some small substitutions.
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
-
We import
platformBrowserDynamic
from the Angular 2 platform browser dynamic. -
We import the
AppModule
we made inapp/app.module.ts
. -
We start up the
platformBrowserDynamic
process and bootstrap theAppModule
.
That's all we need to do to start up our module. Pretty slick.
The Result
If you've been following along, all that's left to do is create a simple web server and visit the index.html
file in the browser.
-
python -m SimpleHTTPServer
-
open http://localhost:8000
Tadaaa!!! A chart rendered with Angular 2 RC5! Party time!
What Now?
Now that we've covered how to make a Angular 2 charts, there are several places to go:
-
This tutorial is based off the Angular 2 quickstart demo from the Angular 2 docs. It's a great place to recap or get more technical explanations of what we did here.
-
The Angular 2: Tour of Heroes tutorial is a great way to start adding more functionality to your app.
As always, 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.