UPDATE: I recently published a WintellectNow course – Getting Started with Ember.js. You should also check out Jeremy Likness’ in depth series on Angular – Mastering AngularJS. Use promo code NSTIEGLITZ-13 for a free two week trial.
Last week, I published a blog post that shows how to build a reusable Star Rating Component using Ember.js. For this blog, I will share how to build the same functionality using a custom directive in Angular.js. I’m not the first person to write a star rating component in Angular.js; this post is more about comparing the bits needed in the respective frameworks.
There are more similarities than differences in the way you build reusable controls, but as you’ll see, there are some key differences. Below is a screenshot of a demo app which uses the Star Rating component.
At the highest level, Angular directives are reusable components used to manipulate the DOM. Angular comes with several built in directives like ngClick, ngRepeat, etc. It’s also pretty straight forward to write custom directives. A custom directives looks just like HTML; you can define a directive to be used as an element, attributes, CSS class, or less commonly, as a comment. For example, here is the ngClick directive (as an attribute):
<button ng-click="count = count + 1" ng-init="count=0">
.csharpcode {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
margin: 0em
}
.csharpcode .rem {
color: #008000
}
.csharpcode .kwrd {
color: #0000ff
}
.csharpcode .str {
color: #006080
}
.csharpcode .op {
color: #0000c0
}
.csharpcode .preproc {
color: #cc6633
}
.csharpcode .asp {
background-color: #ffff00
}
.csharpcode .html {
color: #800000
}
.csharpcode .attr {
color: #ff0000
}
.csharpcode .alt {
width: 100%; margin: 0em; background-color: #f4f4f4
}
.csharpcode .lnum {
color: #606060
}
Here is a an example of using a custom directive as an element:
<customer-address></customer-address>
.csharpcode {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
margin: 0em
}
.csharpcode .rem {
color: #008000
}
.csharpcode .kwrd {
color: #0000ff
}
.csharpcode .str {
color: #006080
}
.csharpcode .op {
color: #0000c0
}
.csharpcode .preproc {
color: #cc6633
}
.csharpcode .asp {
background-color: #ffff00
}
.csharpcode .html {
color: #800000
}
.csharpcode .attr {
color: #ff0000
}
.csharpcode .alt {
width: 100%; margin: 0em; background-color: #f4f4f4
}
.csharpcode .lnum {
color: #606060
}
Read more about Angular Directives here.
Just like an Ember Component, an Angular directive has a template and a corresponding script that handles the DOM manipulation and eventing. Let’s compare the templates…
<ul style="padding: 0"> <li ng-repeat="star in stars" style="color: #FFD700;" class="glyphicon" ng-class="{true: 'glyphicon-star-empty', false: 'glyphicon-star'}[star.empty]" ng-click="click(star.index)"></li> </ul>
<ul style="padding: 0"> {{#each star in stars}} <li style="color: #FFD700;" {{bind-attr class=":glyphicon star.empty:glyphicon-star-empty:glyphicon-star"}} {{action click star}}></li> {{/each}} </ul>
.csharpcode {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
margin: 0em
}
.csharpcode .rem {
color: #008000
}
.csharpcode .kwrd {
color: #0000ff
}
.csharpcode .str {
color: #006080
}
.csharpcode .op {
color: #0000c0
}
.csharpcode .preproc {
color: #cc6633
}
.csharpcode .asp {
background-color: #ffff00
}
.csharpcode .html {
color: #800000
}
.csharpcode .attr {
color: #ff0000
}
.csharpcode .alt {
width: 100%; margin: 0em; background-color: #f4f4f4
}
.csharpcode .lnum {
color: #606060
}
The Angular template is composed using HTML and other directives (both built in and custom). The <LI> element is decorated with three Angular directive attributes.
I won’t review the Ember implementation since I did that in a previous post. One small detail (or big, depending on who you ask) is the Ember implementation uses handelbars.js {{#each}} control flow helper to imperatively loop though each object in stars, compared to Angular’s more declarative ngRepeat directive.
notflixApp.directive('starRating', function(){ return { restrict: 'E', templateUrl: '/app/templates/starRating.html', link: function(scope){ scope.click = function(starRating) { scope.starRating = starRating; scope.ratingChanged({newRating: starRating}); }; scope.$watch('starRating', function(oldVal, newVal) { if (newVal) { scope.stars = []; var starRating = scope.starRating; for(var i = 0; i < scope.maxStarRating; i++){ scope.stars.push({empty:i >= starRating, index:i+1}); } } }); }, scope: { starRating: "=", maxStarRating: "=", ratingChanged: "&", } }; });
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
margin: 0em
}
.csharpcode .rem {
color: #008000
}
.csharpcode .kwrd {
color: #0000ff
}
.csharpcode .str {
color: #006080
}
.csharpcode .op {
color: #0000c0
}
.csharpcode .preproc {
color: #cc6633
}
.csharpcode .asp {
background-color: #ffff00
}
.csharpcode .html {
color: #800000
}
.csharpcode .attr {
color: #ff0000
}
.csharpcode .alt {
width: 100%; margin: 0em; background-color: #f4f4f4
}
.csharpcode .lnum {
color: #606060
}
App.StarRatingComponent = Ember.Component.extend({ maxStars: 0, starRating: 0, stars: [], actions: { click: function(star){ this.set('starRating', star.index); this.sendAction('action', star.index); } }, setRating: function() { var stars = []; var starRating = this.get('starRating'); for(var i = 0; i < this.get('maxStars'); i++){ stars.pushObject(Em.Object.create({empty:i >= starRating, index:i+1})); } this.set('stars', stars); }.observes('starRating').on('didInsertElement') });
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/white-space: pre;/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
In the Angular script, I am adding the custom directive to the notflix demo app. Let’s go through each member on the directive:
There is a bit more code in the Angular directive than in the Ember component. Wiring up the template is not necessary in Ember(because of the naming convention). That’s not to say one is better than the other, you could certainly make the case for being explicit. I have a slight preference for the Ember .observes(..).on(…) syntax vs. the Angular style, but I wouldn’t say it’s objectively better.
The difference in SLOC would become more negligible as the code become more complicated, since the actual custom logic would grow in lock-step between the frameworks.
<div ng-repeat="movie in movies" class="movie-item"> <h1>{{movie.title}}</h1> <star-rating star-rating="movie.starRating" max-star-rating="movie.maxStarRating" rating-changed="update(newRating)"></star-rating> <small>Year Released </small><label> {{movie.releasedYear}}</label> <br/> <small>Critic Review </small><label> {{movie.review}}</label> </div>
.csharpcode {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
font-size: small; font-family: consolas, “Courier New”, courier, monospace; color: black; background-color: #ffffff
}
.csharpcode pre {
margin: 0em
}
.csharpcode .rem {
color: #008000
}
.csharpcode .kwrd {
color: #0000ff
}
.csharpcode .str {
color: #006080
}
.csharpcode .op {
color: #0000c0
}
.csharpcode .preproc {
color: #cc6633
}
.csharpcode .asp {
background-color: #ffff00
}
.csharpcode .html {
color: #800000
}
.csharpcode .attr {
color: #ff0000
}
.csharpcode .alt {
width: 100%; margin: 0em; background-color: #f4f4f4
}
.csharpcode .lnum {
color: #606060
}
<div class="movie-item"> <h1>{{movie.title}}</h1> {{star-rating starRating=movie.starRating maxStars=movie.maxStarRating action="rateMovie"}} <small>Year Released </small><label> {{movie.releasedYear}}</label> <br/> <small>Critic Review </small><label> {{movie.review}}</label> </div>
The <star-rating> usage should feel pretty intuitive. rating-changed calls update on the controller.
Angular’s philosophy is to extends the HTML vocabulary. If you had no experience with HTML, you wouldn’t be able to look at the starRating usage and know whether it is an Angular directive or an HTML element (In fact, when you use the form element in an Angular application, it actually overrides the HTML form element).
In contrast, Ember relies on the {{ handelbars }} syntax to use the component. The action provides a hook into the controller if you need to respond to a user changing the star rating (just like the rating-changed=”…” in the Angular solution.
The usages definitely have more similarities than differences and I don’t have a particularly strong preference for one over the other.
This is a simple example showing only a sliver of the functionality available in each framework. Still, I am surprised by the similarity of the two solutions. The Ember team has said they are not afraid to borrow ideas from others, and the same is probably true for the Angular community. Perhaps both teams borrowed concepts from some other project. It’s also worth mentioning that the Angular Directives are definitely not a one to one with Ember Components. They can solve similar problems, but each can also solve other problems not discussed here.
The code for the Angular demo app is here, and the Ember code is here. Enjoy.
Microsoft Azure and Amazon Web Services (AWS) are two of the most popular cloud platforms.…
Cloud management is difficult to do manually, especially if you work with multiple cloud…
Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…
https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…
FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…
Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…
View Comments
Great comparison. Thanks!