Multiple SVGs and CSS in AngularJS (or why SVG has to be inlined)

english mobile

In my previous post, I discussed the steps to create a SVG for use in an AngularJS project, ending with a very simple interactive SVG in Angular. In that fiddle, the SVG was completely inlined. In the case where there is only one inline SVG, it really doesn't matter if all the CSS is internal or external unless you plan to switch style sheets at runtime.

However, once you have more than one SVG, it becomes more efficient to use one style sheet for all of them. Not only that, when you use inline SVG, the SVG XML is part of the HTML DOM, so CSS styles can collide if they have the same name. As in normal CSS, the internal CSS in the SVG that is loaded latest will win in the case of a conflict. Using an external style sheet consolidates all your styles in one place so it's obvious that you're trying to call different visual styles the same thing.

Given that the CSS styles can overwrite each other (not to mention that Angular 2 is dropping support for ng-include), you might want to use one of the other methods of adding SVG to HTML, an img or object tag, so that we can take advantage of the shadow DOM. Unfortunately, AngularJS also can't reach inside the shadow DOM. This is something I realized when I started building my own example where I was going to explore techniques for styling SVG inside the shadow DOM. But since the SVG can't be made interactive with AngularJS, it kind of spoils things. Sorry for the ugly example—I'd already pulled out the CSS before I realized the SVG wasn't going to be interactive.

It's going to be interesting seeing how this plays out with Angular 2's removal of ng-include. I wonder if people will have to go to reading in the SVG XML via HTTP and hand-compiling it to get interactivity, or if Angular 2 has taken out that capability as well.

Preparing SVG for use in AngularJS

english mobile

Most developers are not also designers, but we commonly have to take a static design we get and figure out how to make it interactive. Recently, I received a set of Illustrator files that depicted circuit diagrams that a user needed to interact with to say whether they thought the circuit would have enough power to light one or more light bulbs.

I could have tried to simply create graphics with every possible state of all the bulbs, but I decided that would be difficult to maintain. I could also have tried to overlay each bulb with a div that contained the various states for that bulb, but I thought that would be difficult to make responsive. Instead, I decided to use data binding in AngularJS to allow users to interact directly with the bulbs inside the SVG.

What I found is there are a lot of resources on SVG in various places on the internet, but none pulled together all the pieces needed to accomplish this task. So I figure it’s up to me. Here are the things I needed to find out to make this work:

In this post, we'll get up and running with a simple SVG example. In the next post, we'll look at the different ways you can use SVG in HTML and the plusses and minuses of each.

How to get Illustrator to create a usable SVG file

Let’s imagine that the illustrator file I got from the designer looks something like this:

Illustrator File Structure

Because I know where I’m headed with this, the file is clean, tidy, and organized. More than likely you will have to clean up paths so they don’t have a lot of unnecessary points that will bloat your file size. You may also need to create groups and layers.

Tips: When you export files from Illustrator, each art board will be a different file. The groups and layers control how the objects are organized within that. It is possible to have objects that are in different layers that are on the same art board, and vice versa, so you may need to reorganize the files to fix that.

Avoid using underscores in layer and group names, because AI will encode them in an odd way. If you want an underscore in an object ID, use a space and Illustrator will replace it with an underscore.

At this point, you might be tempted to follow the Illustrator docs and go to File>Export>SVG. You can try, but the sad truth is there is no SVG option under File>Export in Illustrator CC. Instead, you can go to File>Save As. You’ll want to select “Use ArtBoards.”

Save As dialog

Once you click “Save”, it doesn’t save—Hey-ho, counterintuitive dialog! Instead, it will take you to another dialog, where you can select your SVG output options. The defaults are pretty good. Note here that we’ve selected “Style Attributes” for our CSS Properties. What that means is that things like stroke and fill color will be turned into CSS styles in a style tag at the top of the document. For more on the other options, see Demystifying Adobe Illustrator's Advanced Options for SVG Work.

SVG Save Options

Once you click “OK,” you’ll get an SVG file for every artboard. If you open one of these in any XML or text editor, you’ll get something (output1) like this:

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 300" style="enable-background:new 0 0 400 300;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FCEE21;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.st1{stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.st2{fill:#CCCCCC;stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
.st3{fill:#808080;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.st4{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.st5{fill:#FFFFFF;}
.st6{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
</style>
<g id="Bulb_On">
</g>
<g id="Bulb_Off">
</g>
<g id="Circuit_1">
<polyline class="st4" points="158.3,137.1 311.3,137.1 311.3,261.2 83.3,261.2 83.3,137.1 117.4,137.1 154.4,108.1 "/>
<g id="battery">
<rect x="188.5" y="258" class="st5" width="15.1" height="6"/>
<line class="st6" x1="190.5" y1="250" x2="190.5" y2="274"/>
<line class="st6" x1="201.5" y1="244" x2="201.5" y2="278"/>
</g>
<g id="Bulb_1_">
<path class="st4" d="M264.9,76.3c0-18.6-13.9-33.6-31-33.6c-17.1,0-30.8,15.1-30.8,33.6c0,13.7,7.5,25.4,18.5,30.7v21.6h25v-21.6
C257.5,101.7,264.9,89.9,264.9,76.3z"/>
<rect x="230.5" y="131.3" class="st1" width="6.8" height="5.6"/>
<path class="st2" d="M246.6,129.6c-2.6,1-6.6,2.3-11.7,2.4c-6.1,0.2-10.9-1.3-13.7-2.4c0-5.9,0-11.8,0-17.7
c2.1,1.1,6.6,3.3,12.8,3.3c6.1,0,10.5-2.1,12.6-3.3C246.6,117.8,246.6,123.7,246.6,129.6z"/>
</g>
</g>
<g id="Circuit_2">
</g>
</svg>

I don't know about you, but I'd like some more informative style names. So, back in the Illustrator file, we'll create and apply some named styles, like so:

Illustrator file with named styles applied

Once you do that, your SVG should look more like this:

  • <?xml version="1.0" encoding="utf-8"?>
    <!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
    viewBox="0 0 400 300" style="enable-background:new 0 0 400 300;" xml:space="preserve">
    <style type="text/css">
    .outline{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
    .bulb_base{fill:#CCCCCC;stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
    .bulb_default{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
    .cover{fill:#FFFFFF;}
    .bulb_connect{stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
    .st0{fill:#FCEE21;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
    .st1{fill:#808080;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
    </style>
    <g id="Bulb_On">
    </g>
    <g id="Bulb_Off">
    </g>
    <g id="Circuit_1">
    <polyline class="outline" points="158.3,137.1 311.3,137.1 311.3,261.2 83.3,261.2 83.3,137.1 117.4,137.1 154.4,108.1 "/>
    <g id="battery">
    <rect x="188.5" y="258" class="cover" width="15.1" height="6"/>
    <line class="outline" x1="190.5" y1="250" x2="190.5" y2="274"/>
    <line class="outline" x1="201.5" y1="244" x2="201.5" y2="278"/>
    </g>
    <g id="Bulb_1_">
    <path class="bulb_default" d="M264.9,76.3c0-18.6-13.9-33.6-31-33.6c-17.1,0-30.8,15.1-30.8,33.6c0,13.7,7.5,25.4,18.5,30.7v21.6
    h25v-21.6C257.5,101.7,264.9,89.9,264.9,76.3z"/>
    <rect x="230.5" y="131.3" class="bulb_connect" width="6.8" height="5.6"/>
    <path class="bulb_base" d="M246.6,129.6c-2.6,1-6.6,2.3-11.7,2.4c-6.1,0.2-10.9-1.3-13.7-2.4c0-5.9,0-11.8,0-17.7
    c2.1,1.1,6.6,3.3,12.8,3.3c6.1,0,10.5-2.1,12.6-3.3C246.6,117.8,246.6,123.7,246.6,129.6z"/>
    </g>
    </g>
    <g id="Circuit_2">
    </g>
    </svg>

Hand-editing the files

This file is in much better shape. It has some extra things you'll need to remove, but all of that is obvious. First, you need to remove the comment. After that, you can remove the styles that are in the form .stx (where x is some number). Those are the styles that are not used by the graphics that were on the artboard that became this particular document. Then you need to remove the empty "g" tags that, again, represent the other art boards.

We're only going to use the two circuit diagrams in Angular, but we need to combine the two bulbs into one "g" tag and replace the existing bulb in each circuit diagram with it. One of the goals is to have a "g" where all the graphics are relative to the zero point so we can just translate it to where the empty bulb is in the circuit diagram. This is because the syntax of the path objects doesn't lend itself to easily being moved around in the SVG file, so it's easier to create the objects at the zero point and move the entire group they are in. Unfortunately, we can't just use the bulbs output as SVG because they didn't respect the artboard bounds used in the document. So we're going to go back to Illustrator and create a new file with all the needed shapes by copying and pasting pieces from our existing file.

Bulb with both on and off text in Illustrator

If you simply copy all the contents of this document to your clipboard, your clipboard will contain something like this:

<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In  -->
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="65px" height="97.2px" viewBox="0 0 65 97.2" style="overflow:scroll;enable-background:new 0 0 65 97.2;"
xml:space="preserve">
<style type="text/css">
.bulb_base{fill:#CCCCCC;stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
.bulb_connect{stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.bulb_off{fill:#808080;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
</style>
<defs>
</defs>
<g id="Bulb_1_">
<path class="bulb_off" d="M63.5,35.1c0-18.6-13.6-33.6-30.8-33.6C15.6,1.5,1.5,16.6,1.5,35.1c0,13.7,7.1,25.4,18.1,30.7v21.6h25
V65.8C55.6,60.5,63.5,48.8,63.5,35.1z"/>
<rect x="28.4" y="90.1" class="bulb_connect" width="6.8" height="5.6"/>
<path class="bulb_base" d="M44.6,88.4c-2.6,1-6.6,2.3-11.7,2.4c-6.1,0.2-10.9-1.3-13.7-2.4c0-5.9,0-11.8,0-17.7
C21.2,71.9,25.8,74,32,74c6.1,0,10.5-2.1,12.6-3.3C44.6,76.6,44.6,82.5,44.6,88.4z"/>
<g id="Off_text_1_">
<path class="text" d="M12.3,39.9c0-9.9,15.1-10.5,15.6-0.6c0.2,4.9-2.7,8.3-7.6,8.3C15.5,47.6,12.3,44.6,12.3,39.9z M17,39.9
c0,2.1,0.7,4.2,3.1,4.3c2.6,0,3.2-2.4,3.1-4.5C23.2,34.4,17,34.6,17,39.9z"/>
<path class="text" d="M30.4,32.3h11.5v3.2h-6.8v2.6h5.8v3h-5.8v6.1h-4.7V32.3z"/>
<path class="text" d="M44.4,32.3h11.5v3.2h-6.8v2.6h5.8v3h-5.8v6.1h-4.7V32.3z"/>
</g>
<g id="On_text_1_">
<path class="text" d="M17.3,41.4c0-9.9,15.1-10.5,15.6-0.6c0.2,4.9-2.7,8.3-7.6,8.3C20.5,49.2,17.3,46.2,17.3,41.4z M22,41.5
c0,2.1,0.7,4.2,3.1,4.3c2.6,0,3.2-2.4,3.1-4.5C28.2,36,22,36.2,22,41.5z"/>
<path class="text" d="M35.4,33.9h4.3l5.7,8.3v-8.3h4.4v15h-4.4l-5.6-8.3V49h-4.4V33.9z"/>
</g>
</g>
</svg>

Note that if you copy a selection that contains actual text, you will not get SVG on your clipboard. Instead, you'll just get the text.

The part we want is the "g" with the id of "Bulb_1." I'm going to clean up and rename some things to remove the unnecessary numbers Illustrator is putting at the end of many of our g id's. I'll also change the path that has a class of "bulb_off" to have no class, but instead to have an id of "bulb_bg." We'll be changing the fill of this path and the visibility of the "off" and "on" text based on whether the bulb is on or off. We'll wind up with this:

<g id="Bulb">
<path id="bulb_bg" d="M63.5,35.1c0-18.6-13.6-33.6-30.8-33.6C15.6,1.5,1.5,16.6,1.5,35.1c0,13.7,7.1,25.4,18.1,30.7v21.6h25
V65.8C55.6,60.5,63.5,48.8,63.5,35.1z"/>
<rect x="28.4" y="90.1" class="bulb_connect" width="6.8" height="5.6"/>
<path class="bulb_base" d="M44.6,88.4c-2.6,1-6.6,2.3-11.7,2.4c-6.1,0.2-10.9-1.3-13.7-2.4c0-5.9,0-11.8,0-17.7
C21.2,71.9,25.8,74,32,74c6.1,0,10.5-2.1,12.6-3.3C44.6,76.6,44.6,82.5,44.6,88.4z"/>
<g id="Off_text">
<path class="text" d="M12.3,39.9c0-9.9,15.1-10.5,15.6-0.6c0.2,4.9-2.7,8.3-7.6,8.3C15.5,47.6,12.3,44.6,12.3,39.9z M17,39.9
c0,2.1,0.7,4.2,3.1,4.3c2.6,0,3.2-2.4,3.1-4.5C23.2,34.4,17,34.6,17,39.9z"/>
<path class="text" d="M30.4,32.3h11.5v3.2h-6.8v2.6h5.8v3h-5.8v6.1h-4.7V32.3z"/>
<path class="text" d="M44.4,32.3h11.5v3.2h-6.8v2.6h5.8v3h-5.8v6.1h-4.7V32.3z"/>
</g>
<g id="On_text>
<path class="text" d="M17.3,41.4c0-9.9,15.1-10.5,15.6-0.6c0.2,4.9-2.7,8.3-7.6,8.3C20.5,49.2,17.3,46.2,17.3,41.4z M22,41.5
c0,2.1,0.7,4.2,3.1,4.3c2.6,0,3.2-2.4,3.1-4.5C28.2,36,22,36.2,22,41.5z"/>
<path class="text" d="M35.4,33.9h4.3l5.7,8.3v-8.3h4.4v15h-4.4l-5.6-8.3V49h-4.4V33.9z"/>
</g>
</g>

Next, we'll go back into one of our circuit svg files and replace the plain bulb with this bulb, translated to where the original bulb was. I usually look in illustrator at the properties or transform panel in the original file to see where it was.

The edited svg looks like this:

<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 300" style="enable-background:new 0 0 400 300;" xml:space="preserve">
<style type="text/css">
.outline{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.bulb_base{fill:#CCCCCC;stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
.bulb_default{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.cover{fill:#FFFFFF;}
.bulb_connect{stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
</style>
<g id="Circuit_1">
<polyline class="outline" points="158.3,137.1 311.3,137.1 311.3,261.2 83.3,261.2 83.3,137.1 117.4,137.1 154.4,108.1 "/>
<g id="battery">
<rect x="188.5" y="258" class="cover" width="15.1" height="6"/>
<line class="outline" x1="190.5" y1="250" x2="190.5" y2="274"/>
<line class="outline" x1="201.5" y1="244" x2="201.5" y2="278"/>
</g>
<g id="Bulb" transform="translate(201, 41)">
<path id="bulb_bg" d="M63.5,35.1c0-18.6-13.6-33.6-30.8-33.6C15.6,1.5,1.5,16.6,1.5,35.1c0,13.7,7.1,25.4,18.1,30.7v21.6h25
V65.8C55.6,60.5,63.5,48.8,63.5,35.1z"/>
<rect x="28.4" y="90.1" class="bulb_connect" width="6.8" height="5.6"/>
<path class="bulb_base" d="M44.6,88.4c-2.6,1-6.6,2.3-11.7,2.4c-6.1,0.2-10.9-1.3-13.7-2.4c0-5.9,0-11.8,0-17.7
C21.2,71.9,25.8,74,32,74c6.1,0,10.5-2.1,12.6-3.3C44.6,76.6,44.6,82.5,44.6,88.4z"/>
<g id="Off_text">
<path class="text" d="M12.3,39.9c0-9.9,15.1-10.5,15.6-0.6c0.2,4.9-2.7,8.3-7.6,8.3C15.5,47.6,12.3,44.6,12.3,39.9z M17,39.9
c0,2.1,0.7,4.2,3.1,4.3c2.6,0,3.2-2.4,3.1-4.5C23.2,34.4,17,34.6,17,39.9z"/>
<path class="text" d="M30.4,32.3h11.5v3.2h-6.8v2.6h5.8v3h-5.8v6.1h-4.7V32.3z"/>
<path class="text" d="M44.4,32.3h11.5v3.2h-6.8v2.6h5.8v3h-5.8v6.1h-4.7V32.3z"/>
</g>
<g id="On_text>
<path class="text" d="M17.3,41.4c0-9.9,15.1-10.5,15.6-0.6c0.2,4.9-2.7,8.3-7.6,8.3C20.5,49.2,17.3,46.2,17.3,41.4z M22,41.5
c0,2.1,0.7,4.2,3.1,4.3c2.6,0,3.2-2.4,3.1-4.5C28.2,36,22,36.2,22,41.5z"/>
<path class="text" d="M35.4,33.9h4.3l5.7,8.3v-8.3h4.4v15h-4.4l-5.6-8.3V49h-4.4V33.9z"/>
</g>
</g>
</g>
</svg>

If you preview the SVG in a browser, it looks like this, because we haven't defined the on and off classes or applied them:

Circuit diagram with black bulb

We'll add the styles for the bulb based on the named styles in our exported svg with the on and off bulbs in them. I usually hard-code the class for each state just to make sure they look the way I expect. This is the code for the "on" state:

<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 300" style="enable-background:new 0 0 400 300;" xml:space="preserve">
<style type="text/css">
.outline{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.bulb_base{fill:#CCCCCC;stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
#bulb_bg{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
#On_text, #Off_text {visibility:hidden}
.cover{fill:#FFFFFF;}
.bulb_connect{stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.off #bulb_bg {fill:#808080;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.on #On_text, .off #Off_text {visibility:visible}
.on #bulb_bg {fill:#FCEE21;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
</style>
<g id="Circuit_1">
<polyline class="outline" points="158.3,137.1 311.3,137.1 311.3,261.2 83.3,261.2 83.3,137.1 117.4,137.1 154.4,108.1 "/>
<g id="battery">
<rect x="188.5" y="258" class="cover" width="15.1" height="6"/>
<line class="outline" x1="190.5" y1="250" x2="190.5" y2="274"/>
<line class="outline" x1="201.5" y1="244" x2="201.5" y2="278"/>
</g>
<g id="Bulb" class="on" transform="translate(201, 41)">
<path id="bulb_bg" d="M63.5,35.1c0-18.6-13.6-33.6-30.8-33.6C15.6,1.5,1.5,16.6,1.5,35.1c0,13.7,7.1,25.4,18.1,30.7v21.6h25
V65.8C55.6,60.5,63.5,48.8,63.5,35.1z" />
<rect x="28.4" y="90.1" class="bulb_connect" width="6.8" height="5.6"/>
<path class="bulb_base" d="M44.6,88.4c-2.6,1-6.6,2.3-11.7,2.4c-6.1,0.2-10.9-1.3-13.7-2.4c0-5.9,0-11.8,0-17.7
C21.2,71.9,25.8,74,32,74c6.1,0,10.5-2.1,12.6-3.3C44.6,76.6,44.6,82.5,44.6,88.4z"/>
<g id="Off_text">
<path class="text" d="M12.3,39.9c0-9.9,15.1-10.5,15.6-0.6c0.2,4.9-2.7,8.3-7.6,8.3C15.5,47.6,12.3,44.6,12.3,39.9z M17,39.9
c0,2.1,0.7,4.2,3.1,4.3c2.6,0,3.2-2.4,3.1-4.5C23.2,34.4,17,34.6,17,39.9z"/>
<path class="text" d="M30.4,32.3h11.5v3.2h-6.8v2.6h5.8v3h-5.8v6.1h-4.7V32.3z"/>
<path class="text" d="M44.4,32.3h11.5v3.2h-6.8v2.6h5.8v3h-5.8v6.1h-4.7V32.3z"/>
</g>
<g id="On_text">
<path class="text" d="M17.3,41.4c0-9.9,15.1-10.5,15.6-0.6c0.2,4.9-2.7,8.3-7.6,8.3C20.5,49.2,17.3,46.2,17.3,41.4z M22,41.5
c0,2.1,0.7,4.2,3.1,4.3c2.6,0,3.2-2.4,3.1-4.5C28.2,36,22,36.2,22,41.5z"/>
<path class="text" d="M35.4,33.9h4.3l5.7,8.3v-8.3h4.4v15h-4.4l-5.6-8.3V49h-4.4V33.9z"/>
</g>
</g>
</g>
</svg>

It looks like this in the browser:

Circuit with bulb on

Now that we know all our styles look right, we're going to edit the Bulb group to be interactive:

<g id="Bulb" data-ng-class="bulb.state" data-ng-click="toggleBulbState()" transform="translate(201, 41)">
...
</g>

A Simple AngularJS Project with Interactive SVG

Spying on AngularJS Services That Are called Directly as Functions

english mobile

Jasmine spies are great. But when testing AngularJS services, it can really be be hard to test that the correct calls are being made to services where you’re calling the service itself instead of an attached method, such as the $interval service. When you’re testing controllers, you can directly inject your own spy if you want to test that $interval was called with the correct arguments, but how do you get hold of a function that’s not attached to an object to be able to spy on it?
It turns out that your only option is to replace the service with your spy in the config phase with either $provide.decorator, or module.decorator. For your TL;DR convenience, here is a working module that you can add to the list of modules you instantiate before each test. I’ll explain why all this works after the code.

angular.module('spies.interval', [])
    .decorator('$interval', function($delegate){
       var spy = jasmine.createSpy(‘$interval’).and.callFake(
           function()  {
               return  $delegate.apply(arguments);
           }
       )
       spy.cancel =  angular.bind($delegate, $delegate.cancel);
       spy.flush =  angular.bind($delegate, $delegate.flush);
       spyOn(spy,  'cancel').and.callThrough();
       return spy;
    })

The spy game

Let’s first think about what happens when you call spyOn(someObj, ‘someFunction’), so we can understand why we’re not using that syntax here. Jasmine looks at someObj. If someObj has a function called ‘someFunction’, jasmine overwrites that function with a generic spy which may or may not call the original someFunction. But without someObj, there is nothing to attach that replacement function to to make the replacement invisible to the calling code under test.

Knowing when to delegate

So we have to “trick” Angular into injecting our replacement into the code under test when the code asks for the $interval service. We do this by returning the actual spy rather than an altered $delegate.
This then becomes very different from injecting a fake $interval service into a controller that’s created for just one test, because when you call $interval(), it caches the method to call after the elapsed time, the elapsed time, and so forth internally so that it can work its magic. So if you only replace $interval with a spy, you can no longer test that the right things happen after the time has elapsed, because that internal state isn’t right.
So we need to go on to call the actual $interval service, and we need to call it with the arguments that our service under test is using, which is why we use apply(). Once we’ve done that, we need to make sure to return the result of the call, a promise, so that when we test that code that should be canceled in our client code does not run, that doesn’t break because there’s no promise to give cancel().
Once you’ve taken all of the above into account, you have
var spy = jasmine.createSpy(‘$interval’).and.callFake(
    function() {
        return $delegate.apply(arguments);
})

Spies upon spies

 But you can’t call cancel yet (or flush), because the spy you’re replacing $interval with doesn’t have a cancel() method or a flush() method. If you try just directly adding the functions (spy.cancel = $delegate.cancel), the functions don’t run in the same scope that we wrote to when we called $delegate.apply().  By using angular.bind, we force them into the same scope as the delegate like so:
spy.cancel = angular.bind($delegate, $delegate.cancel);
spy.flush = angular.bind($delegate, $delegate.flush);
Finally, we spy on the cancel function. This is just a convenience—you can of course spy on this function as normal from any test.
spyOn(spy, 'cancel').and.callThrough();
And that, folks, is how you create a spy to be able to check that an angular service where the function is called directly was called with the correct methods without breaking the service.

But wait, there’s more

If you’re a Jasmine expert, you’re probably thinking that I could have just used the undocumented second argument of createSpy, which simplifies the code to this:
angular.module('spies.interval', [])
     .decorator('$interval', function($delegate){
        var spy =  jasmine.createSpy('$interval', $delegate);
        spyOn(spy,  'cancel').and.callThrough();
        return spy;    
})
Unfortunately, that doesn’t work. You would expect the Strategy to do the same thing internally that my home-rolled version does, but it doesn’t. Maybe some other day I’ll dig through the code and blog about why.

How useful is this anyway?

I think the main reason to use this is when the amount of time you need to wait or the optional arguments at the end will vary based on some data. However, your test usually can’t even see the method that ultimately gets called by $interval. So it feels a bit like it should not need to know so much about it that it is looking at what arguments are being passed to this function it can’t see.
So after all this, I ultimately came to feel that though you can do this, you probably shouldn’t, because it introduces a lot of coupling between your test and the internals of the methods in your service that you’re trying to test. For example, your test should not need to know whether your service internally creates its own closure to store state to call the method from in $interval or whether it directly stores that state in the end parameters of $interval.
In Scotland, they have a saying that a gentleman is a man who knows how to play the bagpipes, but doesn’t. I often feel that programming is like that.

Tips for Testing AngularJS SPA's with Karma and Jasmine

english mobile

When I first started unit testing with AngularJS, Karma, and Jasmine, I read everything I could get my hands on about the subject. While there’s a lot of good material out there, I wouldn’t want to write tests, let alone ask my team to do so based on the material I originally found. This post is the one I wish I’d found when I first started writing tests, less of a “just how to get it working” and more of a “how to make writing tests a sustainable part of your process.” Some of these tips seem relatively trivial, but even little things can add up to make writing tests easier for a team.

Angular.mock.dump() and karma.dump()

One thing that is hard about unit testing with Karma is it’s not that practical to set break points. When I first started doing unit testing, I didn’t even know that you could use console.log() to print out useful messages from the tests and code.

When I figured that out, it helped quite a bit. However, complex objects could get hard to read and sometimes even would be truncated. It turns out that angular.mock has a method, dump() that will format the object to be legible from the command prompt window. Karma also has a dump() function that’s basically a shortcut to console.log().

Setting flags to only print information from certain tests

One thing that’s a little scary about using angular.mock.dump() in the code under test is that you’re going to get an error when you try to run that code and it’s not from a test. In addition, if a lot of tests run a particular piece of code, it can be difficult finding the output that matters to the test you’re working on from a firehose of output statements.

I like to give my AngularJS “classes” a method that sets a flag to turn these statements on and off. I usually call it setDebug(), and then I will call that from the test I’m currently working on (or in a beforeEach, depending on how early I need it set).

Shared Data Sources

It’s pretty common for multiple Services and Controllers to need similar data sets in their tests, so the data sets are easier to use and maintain if they are not kept in each spec, which is what you’ll see in most test examples on the internet. The biggest thing that has made testing sustainable for my team is to develop a convention for our data sources, both in how they’re named and how they’re structured.

The service name will usually be “myDataTypeData”. In turn, the mocks that are mocking services that retrieve those data sets can be given a path of “myDataType” and will return the data from the myDataTypeData service by asking for it from the injector.

Each data service has a method, “getAllData,” that returns converted json from an entire XML file, including a collection of data items with some header information that pertains to the collection as a whole. It also has a “getNormalizedDataItems” method that returns the collection of data items pre-processed to remove the XML artifacts. So tests that are testing large pieces of the application and tests that are testing smaller pieces of the application can get the data they need.

Balancing DAMP with DRY

Most of us have heard of DRY (Don’t Repeat Yourself) for production code, which means that rather than duplicating code to other places that need similar functionality, you extract that code and call it from all the different places that need it. Unit tests often are running almost the same code, but with slightly different inputs. One of the purposes of tests is to document what the software should do, so developers need to be able to see at a glance what the inputs are, where they are going, and what the expected outputs are. So tests are often written to be DAMP (Descriptive and Meaningful Phrases), which can translate to a lot of repeated code.

If you’ve ever wanted to make a major change in your code and felt that sinking feeling because you knew that too many places referenced it directly, you understand why DRY is a good thing. Change things in one place and you’re done. It can be hard to make a business case for tests to non-technical managers, so the last thing you ever want to say is “yes, we can make that change but it will take a while because there are a lot of tests around that functionality.”

After being bitten by that one, I developed a strategy that works in part because of my shared data sources. Most Jasmine specs have a setup() method. The first argument will usually be the data source name. If we’re testing at the individual data item level, the second argument will be the index of the data item I want.

Here’s where it gets tricky, because the setup step is usually going to instantiate something, whether it’s $controller building a controller for me to test with the data item attached to its $scope or a Factory (not an angular factory, specifically, but the Factory pattern) that is responsible for turning that data item into something else. So customization often needs to happen in between when we retrieve the data from the service and when we construct the item.

To handle that, I use a third parameter that takes a function to perform whatever extra massaging I need to customize the data before the actual object gets made. This means that once you know the pattern, you can read setup(‘someType’, 2, function (dataItem) dataItem.foo=’bar’}), and know exactly what is going on. In most cases, it’s not critical to see all the steps to retrieve the data and make the thing, with all the $rootScope.apply() calls that keep angular happy. What you care about is what’s special about this data as an input.

The flip side of that is that most examples you’ll see on blogs, etc., have a beforeEach() method at the top of the spec that pulls absolutely everything you’ll need from the $injector and stores it in a variable. This can get kind of hard to read, especially if for whatever reason you don’t use the actual name of the service as the variable name. And of course there’s that little part of my developer soul that is slightly bothered by exposing a variable for an entire spec that is only used in one place.

It took me a while to realize it, but you can use inject() any place that takes a Function in a spec. So, you can put an inject() in any beforeEach, not just the first one, or as the second parameter for a describe() or it() statement. Probably the only thing stopping you from injecting at the top-level describe block and simplifying your variable declarations is the need to bootstrap the module(s) under test.

Modularization

When I took over the project I am currently leading, it was basically a prototype grown out of control. It had no tests and few modules. Our first priority was to start writing tests on new code, and then we started breaking out modules based on features. But our tests all bootstrapped the full app. The tests began to take longer and longer to run, so we knew that had to stop. Our first efforts at just bootstrapping a few modules in tests broke the tests pretty hard, so we put that task on the back burner while we continued to think about it.

Finally, it occurred to me that we had made a bad assumption when we decided what to leave in the main “app” module. It seemed logical at the time to leave pieces that were used in a lot of places in the app in the app itself. That was exactly backward, because any test that tested anything that depended on any of these commonly-used services had to bootstrap the app, which bootstraps everything. So the lesson learned there is “the more places it’s used, the more it needs to be in its own module.”

We also added a “mocks” module, which contains overrides of some of the services that we don’t want to really use in tests (like the one that plays audio or the one that reads the XML into JSON). By doing it this way, we can choose the real service simply by not bootstrapping “mocks.” The exception to this is sometimes you have to bootstrap the module the service is actually in, and that module can “win.” To resolve that situation, you can register a mock service in the module where the original service was, which will overwrite it, and also register it in the mocks module.

What are your tips?

I by no means claim that these tips and tricks are the best way to do things, but they are things I have found out for myself through trial and error that I have found helpful. Maybe you’ll find them helpful. If you have some killer ways to rock your jasmine tests, please feel free to share them in the comments.