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 BarchartComponent
and BarchartDataDirective
each have the same set of inputs – BarchartComponent
just forwards them along to BarchartDataDirective
. 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 ViewChild
decorator.
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.
This is absolutely awesome!!!!! I’m using Fabricjs and this helped me get to the canvas. Thank you!