Using the Canvas API in Angular2
Angular 2.0 has finally been officially released. To help develop my own understanding of it and surrounding technologies, I’ve been working on building a simple little multiplayer card game with it. This got me wondering about how to work with the Canvas API in an Angular2 application. None of the tutorials, articles, or books I’ve read have touched on this particular use case. By experimenting, digging into the Angular 2 source, and scouring the web, I came up with a solution that seems work well. I setup a github repo which you can check out and run yourself: https://github.com/sflahave/angular2-canvas-examples . This repo was copied from the Angular2 QuickStart repo, so if you’ve gone through the QuickStart tutorial, the setup should look familiar.
First (lame) Attempt
My first attempt at working with the Canvas involved modifying the example code in the Attribute Directives tutorial from the official Angular2 documentation. Following the example set in that tutorial, I created an attribute directive. The file barchart.component.ts from my repo gives a working example. A screenshot is shown below. The essential structure is that there is a BarchartComponent whose template has a <canvas> element. That <canvas> element has an attribute directive, BarchartDataDirective, applied to it. BarchartDataDirective’s constructor asks Angular to give it an
ElementRef (through Angular’s dependency injection mechanism). It uses ElementRef’s nativeElement property to access the <canvas> element, which is needed in order to get the graphics context for Canvas API operations.
Why did I do it this way? Well, I had some trouble figuring out what I could do with
ElementRef and it’s
nativeElement property. I wanted to just use the single component, and put the <canvas> element in the component’s template. Then, I figured I could just query the
nativeElement of the
ElementRef passed to the constructor to get a handle on the embedded <canvas> element. But I couldn’t find a good way (at first, as we’ll see) to get at the <canvas> element unless it was the thing
ElementRef referred to. By applying an attribute directive directly on the canvas, the
ElementRef passed into
BarchartDataDirective's constructor refers to the canvas, allowing me to get a handle on it.
This setup works, but it’s not very satisfying. Notice that
BarchartDataDirective assumes/expects that it is being applied to a <canvas> element. That’s not good. Also, the
BarchartDataDirective each have the same set of inputs –
BarchartComponent just forwards them along to
BarchartComponent doesn’t really do anything except setup the template and serve as a sort of public interface. We can improve on this by making use of the
A better approach via @viewchild decorator
Now, take a look at Barchart2Component. Using the
ViewChild decorator and a template reference variable, we can get rid of the
BarchartDataDirective and just use
BarchartComponent. I apply a template reference variable to the <canvas> element, giving it the name “canvas”, and then setup an
ElementRef property in
BarchartComponent that is decorated with
@ViewChild('canvas'). Now we have a reference to the child <canvas> element, which is what I originally wanted. However, note that it won’t be ready in the constructor, so I added an
ngAfterViewInit() method where I can grab the native <canvas>, set it’s dimensions, and finally invoke the drawing routine. Muhhhhhhch better.
I whipped up a
PieChartComponent using the same technique. Here are some screenshots. (By the way – the actual canvas drawing code for these charts were adapted from examples in the online book HTML Canvas Deep Dive by Josh Marinacci ).
And if charts aren’t your thing, I also thew together a <smiley> component that draws a smiley face (Canvas code adapted from examples in an MDN canvas tutorial):
With all of these components, you can specify the dimensions and colors. Play around with them, go ahead, have a good time.
words of Warning (alternatively: I know, I know, save it…)
Direct DOM manipulation is generally frowned upon in Angular2 (see the comments for ElementRef in the Angular2 source code) but, as far as I know, there’s no other way to use the Canvas API. Actually I’m not sure if the techniques I’ve shown here really count as “direct DOM manipulation”, since we’re not adding or removing DOM elements, just using one as it is intended to be used.
one last thing…
Finally, if you’re interested in the topic of Canvas+Angular2, you may have run across the article Rendering in Angular2. While it’s an interesting read, it doesn’t address the same “problem” I had in mind. Instead, it describes how you might swap the default
DomRenderer with an alternate renderer, such as (for example) a
CanvasRenderer to render your entire application in a canvas element (or even an Electron renderer). That’s my understanding, anyway. Apparently it’s still experimental. It’s definitely something to watch.