Over the last couple of weeks I’ve been fortunate enough to play around in both Ember and Angular. Having spent the last 18 months on an Ember project I’m very much in the Ember camp and was really rooting for Ember to end up the heavy weight champ. However what I found was that I’d be happy to work in either framework and believe that both have reached a maturity level that allows developers to think about solving high level problems instead of building glue (most of the time…). With the niceties out of the way lets get dirty and compare some of the differences in each framework. In this post we’ll look at templates.
My least favourite part in Angular is their templating syntax. Angular uses plain HTML with custom DOM attributes attributes for control flow (ng-hide, ng-show, ng-repeat). Whereas Ember uses Handlebars which supports more familiar control flow statements (if, else, each). Personally I prefer the Ember approach as it’s similar to most templating languages I have used (erb, haml, etc) and it’s easier for my CPU (aka brain) to scan and parse.
Lets look at a couple of examples.
Hide & Show
Let’s say I have a button that toggles the sort order of a list. When the list is sorted in ascending order it shows a “^” character and when it’s sorted in descending order it shows a “∨” character.
In Angular I could use the ng-show property along with a boolean operator!
<button>
<span ng-show="sortAscending">^</span>
<span ng-show="!sortAscending">∨</span>
</button>
Developers that like a little less logic in their templates could use ng-show & ng-hide instead.
<button>
<span ng-show="sortAscending">^</span>
<span ng-hide="sortAscending">∨</span>
</button>
In Ember you can use an if-else statement within your Handlebars template
<button>
{{#if sortAscending}}
^
{{else}}
∨
{{/if}}
</button>
Or for those that like the pain you could use unless-else
<button>
{{#unless sortAscending}}
∨
{{else}}
^
{{/if}}
</button>
There are a couple of things I don’t like about the Angular approach above
- Angular forces me to do mental arithmetic with hide & show. Does a truthy value to ng-hide, show or hide the span? What about the inverse?
- Angular leaves both the hidden and displayed span in the DOM and assigns a display: none style attribute to the element that should be hidden. When I look at the DOM in Chrome inspector I prefer my DOM to be a reflection of what I see on the screen. When Ember sets up the if-else block it watches that property and adds/removes the block from the DOM that doesn’t need to be displayed.
- When building a toggle control like the one above I need to either repeat the <button> or the <span> in Angular. Ember lets me switch the content of my button without repeating myself.
- The Ember code almost reads like a sentence. I read the first Ember example as:
If sortAscending is true then show an up arrow else show a down arrow
The Angular code doesn’t read like a sentence. I have to put in some effort to deconstruct each ng-show/ng-hide attribute and re-arrange the code as words in my sentence. It might go something like:Show the first span when sortAscending is true
Don't show the second span when sortAscending is true
Where’s The Logic?
Keeping logic out of the view is one of the best practices preached by many a framework these days. Angular is no different. In the Angular documentation it states:
application logic should be in controllers, not in the view
I don’t see this throughout their template design. Angular attributes can take complex expressions or filters. This makes quickly wiring up complex UI interactions a snap but a pain to test. If a development team doesn’t know better, or try to enforce best practices, it’s very easy to write code like this:
<div ng-show="people.length>100 && user.isFree">
You've got too many friends
</div>
Following Angular best practices would yield something like this.
<div ng-show="hasTooManyFriends()">
You've got too many friends
</div>
Handlebars only allows boolean logic within their control flow constructs. Meaning we would have to follow the same best practices Angular preaches.
{{#if hasTooManyFriends}}
You've got too many friends
{{/if}}
Enumeration
At the moment enumeration in Angular works for most cases. However there are currently a couple of uses cases that cause it to fall over. To render a list of people I can use the ng-repeat attribute on an element.
<div ng-repeat="person in registrations">
Hi I'm {{person.firstName}} {{person.lastName}}
</div>
I think the core team have really nailed rendering lists in Ember. Lets take a look at some of their tasty syntax sugar by using the each construct.
{{#each person in registrations}}
Hi I'm {{person.firstName}} {{person.lastName}}
{{/each}}
Did you notice that Angular requires a wrapper element to repeat? Now don’t get me wrong most of the time you’re probably going to want an element to wrap your item’s content. The gripe I have here is that Angular forces me to always wrap the content I want to repeat in only 1 element.
Lets look at a more complicated example. This one’s straight out of the Ember vs Angular cage match that has been doing the rounds. My feature calls for me to render a set of tabular data for each person that has registered for one of my special offers. However our designer has thrown us a bit of a curveball. She want’s the persons name on one row and all of their meta information (age, sex, etc) on a second row.
In Angular I can’t repeat two <tr> elements. I’m going to have to add some extra wrapper tags within my row.
<table>
<tr ng-repeat="person in registrations">
<div>{{person.firstName}} {{person.lastName}}</div>
<div>{{person.age}}, {{person.sex}}</div>
</tr>
</table>
In Ember I’d use the familiar each construct as it repeats everything in the provided block.
<table>
{{#each person in registrations}}
<tr>{{person.firstName}} {{person.lastName}}</tr>
<tr>{{person.age}}, {{person.sex}}</tr>
{{/each}}
</table>
There is some work being done to rectify this problem in future releases of Angular. However as you can probably tell I’m not really a fan of the proposed solution that uses ng-repeat-start & ng-repeat-end. How would I repeat 3 rows?
To finish up enumeration lets notify the user that no one has signed up yet. We can keep building on our existing example. To display the empty message in Angular we’re going to need to bring back ng-hide from earlier.
<table>
<tr ng-repeat="person in registrations">
<div>{{person.firstName}} {{person.lastName}}</div>
<div>{{person.age}}, {{person.sex}}</div>
</tr>
<tr ng-hide="registrations.length">
It sucks that no one's signed up yet. Check back later eh.
</tr>
</table>
Lets add to our existing Ember example by using the handy else case in conjunction with each
<table>
{{#each person in registrations}}
<tr>{{person.firstName}} {{person.lastName}}</tr>
<tr>{{person.age}}, {{person.sex}}</tr>
{{else}}
<tr>It sucks that no one's signed up yet. Check back later eh.</tr>
{{/each}}
</table>
I prefer the Ember approach for a couple of reasons.
- I can scan the Handlebars template where the else sticks out which clearly indicates it’s doing something different in the enumeration.
- The extra row in Angular doesn’t immediately stick out. I have to look within the extra <tr> to notice the ng-hide attribute. Not to mention I now need to coalesce the number of registrations into a boolean check for ng-hide
Attribute Bindings
Lets say I have an element and I want to be able to toggle whether or not I can edit it. Something as simple as setting contenteditable=”true” on a <div>. Angular has a really nice and concise syntax to achieve this.
<div contenteditable="{{canEdit}}">Edit me!</div>
Ember on the other hand leaves a lot to be desired. Attribute bindings have their own special syntax which is different to content bindings.
<div {{bindAttr contenteditable="canEdit"}}>Edit me!</div>
It’s worth noting that work is currently being done to bring Ember in line with Angular in this department. It’s slated to drop in Ember 1.1 which at the time of writing has no public release date.
That’s all folks. Hopefully you found some of the differences useful. Either way let me know which style you prefer and why in the comments or hit me up on twitter @rupurt