Creating and using your own AngularJS filters

I have been working on the client-side portion of a rather complex feature and I found myself needing to trim certain things off a string when binding it in my AngularJS code. This sounded like a perfect job for a filter. For those familiar with XAML development on .NET-related platforms like WPF, Silverlight and WinRT, a filter in Angular is similar to a ValueConverter. The set of built in filters for Angular is pretty limited and did not support my desired functionality, so I decided to write new filter of my own called trim. I even wrote some simple testing for it, just to make sure it works.

Testing

For the sake of argument, let's presume I followed TDD or BDD principles and wrote my test spec up front. I used jasmine to describe each of the behaviours I wanted1.

describe('trim filter tests', function () {
	beforeEach(module('awesome'));

	it('should trim whitespace', inject(function (trimFilter) {
		expect(trimFilter(' string with whitespace ')).toBe('string with whitespace');
	}));
		
	it('should trim given token', inject(function (trimFilter) {
		expect(trimFilter('stringtoken', 'token')).toBe('string');
	}));
		
	it('should trim token and remaining whitespace', inject(function (trimFilter) {
		expect(trimFilter(' string token ', 'token')).toBe('string');
	}));
});

An important point to note here is that for your filter to be injected, you have to append the word Filter onto the end. So if your filter is called bob, your test should have bobFilter as its injected parameter.

Implementing the Filter

With the test spec written, I could implement the filter. Like many things in Angular that aren't directives, filters are pretty easy to write. They are a specialization of a factory, returning a function that takes an input and some arbitrary parameters, and returning the filter output.

You add a filter to a module using the filter method. Below is the skeleton for my filter, trim.

var myModule = angular.module('awesome');

myModule.filter( 'trim', function() {
    return function (input, tokenToTrim) {
        var output = input;
        // Do stuff and return the result
        return output;
    };
});

Here I have created a module called awesome and then added a new filter called trim. My filter takes the input and a token that is to be trimmed from the input. However, currently, the filter does nothing with that token; it just returns the input. We can use this filter in an Angular binding as below.

<p style'font-style:italic'>Add More {{someValue | trim:'Awesome'}} Awesome</p>

You can see that I am applying the trim filter and passing the token, "Awesome". If someValue was "Awesome", this would output:

Add More Awesome Awesome

You can see that "Awesome" was not trimmed because we didn't actually implement the filter yet. Here is the implementation.

myModule.filter('trim', function () {
	return function (input, token) {
		var output = input.trim();

		if (token && output.substr(output.length - token.length) === token) {
			output = output.substr(0, output.length - token.length).trim();
		}
		return output;
	};
});

This takes the input and removes any extra spaces from the start and end. If we have a token and the trimmed input value ends with the token value, we take the token off the end, trim and trailing space and return that value. Our binding now gives us:

Add More Awesome

Perfect.

  1. Try not to get hung up on the quality of my tests, I know you are in awe []

Unit testing attribute driven late-binding

I've been working on a RESTful API using ASP WebAPI. It has been a great experience so far. Behind the API is a custom framework that involves some late-binding. I decorate certain types with an attribute that associates the decorated type with another type1. The class orchestrating the late-binding takes a collection of IDecorated instances. It uses reflection to look at their attributes to determine the type they are decorated with and then instantiates that type.

It's not terribly complicated. At least it wasn't until I tried to test it. As part of my development I have been using TDD, so I wanted unit tests for my late-binding code, but I soon hit a hurdle. In mocking IDecorated, how do I make sure the mocked concrete type has the appropriate attribute?

var mockedObject = new Mock();

// TODO: Add attribute

binder.DoSpecialThing( mockedObject.Object ).Should().BeAwesome();

I am using Moq for my mocking framework accompanied by FluentAssertions for my asserts2. Up until this point, Moq seemed to have everything covered, yet try as I might I couldn't resolve this problem of decorating the generated type. After some searching around I eventually found a helpful Stack Overflow question and answer that directed me to TypeDescriptor.AddAttribute, a .NET-framework method that provides one with the means to add attributes at run-time!

var mockedObject = new Mock();

TypeDescriptor.AddAttribute(
    mockedObject.Object.GetType(),
    new MyDecoratorAttribute( typeof(SuperCoolThing) );

binder.DoSpecialThing( new [] { mockedObject.Object } )
    .Should()
    .BeAwesome();

Yes! Runtime modification of type decoration. Brilliant.

So, why didn't it work?

My binding class that I was testing looked a little like this:

public IEnumerable<Blah> DoSpecialThing( IEnumerable<IDecorated> decoratedThings )
{
    return from thing in decoratedThings
           let converter = GetBlahConverter( d.GetType() )
           where d != null
           select converter.Convert( d );
}

private IConverter GetBlahConverter( Type type )
{
    var blahConverterAttribute = Attribute
        .GetCustomAttributes( type, true )
        .Cast<BlahConverterAttribute>()
        .FirstOrDefault();

    if ( blahConverterAttribute != null )
    {
        return blahConverterAttribute.ConverterType;
    }

    return null;
}

Looks fine, right? Yet when I ran it in the debugger and took a look, the result of GetCustomAttributes was an empty array. I was stumped.

After more time trying different things that didn't work than I'd care to admit, I returned to the StackOverflow question and started reading the comments; why was the answer accepted answer when it clearly didn't work? Lurking in the comments was the missing detail; if you use TypeDescriptor.AddAttributes to modify the attributes then you have to use TypeDescriptor.GetAttributes to retrieve them.

I promptly refactored my code with this detail in mind.

public IEnumerable<Blah> DoSpecialThing( IEnumerable<IDecorated> decoratedThings )
{
    return from thing in decoratedThings
           let converter = GetBlahConverter( d.GetType() )
           where d != null
           select converter.Convert( d );
}

private IConverter GetBlahConverter( Type type )
{
    var blahConverterAttribute = TypeDescriptor
        .GetAttributes( type )
        .OfType<BlahConverterAttribute>()
        .FirstOrDefault();

    if ( blahConverterAttribute != null )
    {
        return blahConverterAttribute.ConverterType;
    }

    return null;
}

Voila! My test passed and my code worked. This was one of those things that had me stumped for longer than it should have. I am sharing it in the hopes of making sure there are more hits when someone else goes Internet fishing for help. Now, I'm off to update Stack Overflow so that this is clearer there too.

  1. Something similar to TypeConverterAttribute usage in the BCL []
  2. Though I totally made up the BeAwesome() assertion in this blog post []

Pie and Pirates

Just as with Hell and Hot Chocolate, this was my entry in a short story contest held among the denizens of http://bbs.chrismoore.com (affectionately known to the Mooreons that frequent it as The Boardello).  The challenge was to write a story with the title Pie and Pirates. It's not the best thing I ever wrote, hastily crafted between CounterStrike:Source games, but it did earn me some gel pirates to stick to the window of my apartment.

Pie and Pirates

by Jeff Yates

“Give me the pie and no one gets hurt!” I shouted so that everyone in the tavern could hear. “Argh!” I added, remembering that I was a pirate and it was kind of a rule.

The warning shot I’d fired a few moments earlier, killing Bearded Bill’s parrot and wounding Bearded Bill (who wasn’t actually bearded or named Bill due to him being only 7 years old and named Tarquin), had already silenced the room, making shouting pretty redundant, but it too was kind of a rule.

“Come on, come on! Avast with the pie already! Argh!” said Bearded Bill (still without a beard), who then turned to me and whispered, “I can’t believe you shot Spongebob.”

“Sorry, matey,” I said, eyeing the corpse of his dead beloved as it lay in a feathered heap on the floor. “I never was very good with a pistol, I’m more of a swashbuckler, myself. We’ll get you another parrot.” I ruffled his hair.

“I can’t believe you shot me,” he continued with surprise, as though he’d forgotten all about it until the blood reminded him.

“I said I was sorry! Focus on the matter at hand?” I was hungry and had no time for Bill’s whinging. I turned back to the room and eyed the occupants with suspicion just in case they were getting any bright ideas. “Avast, ye landlubbers! Get the pie or the parrot won’t be the only one to be meeting his maker! Argh!”

Bill, with only a year at sea, was quite naive in the ways of the pirate. I, however, had been at sea for over two years now and at 9, had seen all the world had to see. I was a ruthless killer. A highwayman of the high seas. The scourge of every…

“Excuse me!” said the tavern keeper from the back of the room, “Number 65?”

Bill checked our ticket. “That’s us!” he called back. I elbowed his ribs and raised my eyebrows. “Oh right, Argh!”

As we walked out of the tavern — me carrying the pie, Bill carrying a dead parrot and nursing his wounded shoulder — the keeper shouted after us, “See you next week, boys!”

We both waved behind us and headed for home.

* * *

It took us fifteen minutes or more to trek back to the ship, but once there, we were greeted with growls and licks from the scurvy dogs aboard.

“Good to see you, mateys!” said Bill to the old seadogs.

“Avast! Ye scurvy dogs. Argh!” I added, “And now for the feast! Argh!”

“Argh!” said Bill.

We both sat down on deck and reached for the pie, eagerly anticipating its taste.

“I hope you two aren’t eating that pie!” shouted Captain Mum.

“Aw, mum!” we whined in unison, “We’re playing pirates!”

“I’ll give you pirates! Bring that pie inside before the dogs get it!”

And so, Bearded Bill and One-Eyed Jack, heads hung in shame, walked the plank into the kitchen and sat down for tea.

Felicity

Every now and then I have extremely vivid dreams that feel so real, I've been known to make a phone call afterwards to check on someone. I'm not usually superstitious, but I get freaked out once in a while. Anyway, I thought it might be amusing to write some of them down so we can see just how messed up my head is at night.

I'll begin with Felicity.

One night I woke up at about 4 or 5 AM, a cold sweat had soaked my pillow and I was arrested with abject terror of whatever might be lurking in the darkness on the other side of the bed sheets. The rational part of me knew already — some lazily strewn clothing, bedroom furniture, and our cats — but the part of me that had been terrified by hallucinations in my sleep was certain something more sinister awaited. The terror was reaffiremd by a hundred or more memories of being terrified in similar situations as a child.

I turned on the light. I knew it would wake Chrissy, but I needed to properly wake up and recover.

"What's wrong?" she yawned.

"A bad dream; a nightmare. That's all," I said.

"Oh, I'm sorry," she said, "what was it about?"

Chrissy has learned to ask for details ever since the first occasion I had a nightmare while she was nearby. That had been in a lovely hotel in Reno around the first  time I met her dad. It was a particularly vivid nightmare to the point I called my parents to quiz them about the quality of the electrical system in their house. However, we'll leave that piece of insanity for another time, back to Felicity.

"It was messed up," I said, "it was a CCTV screen showing my grandma's old bedroom, but decorated as it is now; as it was when we stayed there this summer. The CCTV camera seemed to be somewhere above the window, zoomed in on the top half of the bed, towards the headboard. There was a little girl lying there on the left of the bed. Maybe 6 or 8 years old? She wore pretty floral dress, mostly white with small pink roses or something and a some sort of ribbon as a belt, white socks that stopped an inch or so below the knee, and patent leather shoes with a shiny buckle. Her hair was long and light brown, mousy, maybe dirty blonde, something like that. I had the feeling she had blue eyes but they were closed the whole time. Her arms were by her side, I think, they could have been clutched at her waist. Maybe both. She was dead. It felt as though she was dead."

"That's fucked up," said Chrissy, making sure I was completely aware of what I already knew.

"Right? I don't know when, but at some point in the dream, it stopped being CCTV and we were there in the room. You were there, next to her, or someone that I felt was you; I don't recall seeing you. The weirdest thing is, I knew her name was Felicity. I didn't recognise her, I don't recall anyone in the dream saying it, but I knew."

I paused. Thinking about the completely strange but vivid dream and then said, "Who the fuck is Felicity?"

We both chatted a while, which turned into laughter as we quizzed one another, "Who the fuck is Felicity?" Eventually, I calmed down enough to drift back to sleep and dream of something less disturbing.

We still don't know who Felicity is1 or why I would have dreamed of someone called Felicity at all. I have only ever known one Felicity in my life – a girl with whom I went to primary school. Memories of her would fit the right age range, but it didn't seem like her and I had not even had a conversation about her in over 20 years. For now and maybe hopefully always, this remains a creepy mystery, but just in case, I leave you with that burning question; who the fuck is Felicity?

  1. perhaps was []