Hi everyone. I've been playing for 2 months with Angular JS and going to post a couple articles about it.
I was really impressed by a unique feature of angular js that allows to create custom tags and custom attributes using angular directives.
In our prototype we had a situation where we need to show some product details including product image.
Actually there are 3 valid cases for product image in our application:
So if we have a url for a product lets show and image with that url, if we don't have an image let's use some default image like:
Everything looks simple but:
Actually all magic is inside the link function. We watch ngSrc, it's an url that we set from the markup. If we don't have a url then we use default image. Bellow we attach new event handler to the element and if error event occurs we again assign default image. nsSrc here comes from attr variable, attr variable it's just a collection of all attributes assigned to the html element. You also might have noticed that we wrote restrict: 'A', it means that we are going to use this directive as an attribute over html element. Why do we use scope.$watch !? well let's image a situation when somebody dynamically changes image url, then we will need to reevaluate our url and again display real image or image placeholder.
Here goes example of usage:
To make code more flexible and reusable, we should get rid of hard coded image url inside the directive.
Since Angular JS is built with DI in mind let use that approach. Let's create some settings provider and inject it inside the directive. So our code will look like this:
Now in case we need such directive in other application we easily can include script to the application and define a factory that will return appropriate url.
I was really impressed by a unique feature of angular js that allows to create custom tags and custom attributes using angular directives.
In our prototype we had a situation where we need to show some product details including product image.
Actually there are 3 valid cases for product image in our application:
- we don't have an image url
- we have a valid image url
- we have broken image url (server errors like 404 or 503)
So if we have a url for a product lets show and image with that url, if we don't have an image let's use some default image like:
Our markup will be something like this:
<img ng-show="product.imageUrl" ng-src="{{product.imageUrl}}" /> <img ng-show="!product.imageUrl" src="/demo/img/no_image_small.png" />
It's very important to use ng-src directive instead of plain old src attribute. Why ?!...actually because it takes some time to instantiate and wire up all angular stuff, it's pretty fast but anyway browser will start loading an image using "product.image" like an url, before angular will evaluate this statement.
Let's handle now the situation when url is broken. It's very simple. There is an event called onerror let's add this handler to the first image tag. And let's define a handler for such event.
<img ng-show="product.imageUrl" ng-src="{{product.imageUrl}}" onerror="setDefaultImage(this)" /> <img ng-show="!product.imageUrl" src="/demo/img/no_image_small.png" />
Javascript handler:
function setDefaultImage(image) { image.src = "/demo/img/no_image_small.png"; }
Everything looks simple but:
- It's hard to reuse: we will need to copy everything all the time
- It's complicated and ugly
- We introduced a global function
- It's not the Angular JS way
app.directive('noImage', function () { var setDefaultImage = function (el) { el.attr('src', "/demo/img/no_image_small.png"); }; return { restrict: 'A', link: function (scope, el, attr) { scope.$watch(function() { return attr.ngSrc; }, function () { var src = attr.ngSrc; if (!src) { setDefaultImage(el); } }); el.bind('error', function() { setDefaultImage(el); }); } }; });
Actually all magic is inside the link function. We watch ngSrc, it's an url that we set from the markup. If we don't have a url then we use default image. Bellow we attach new event handler to the element and if error event occurs we again assign default image. nsSrc here comes from attr variable, attr variable it's just a collection of all attributes assigned to the html element. You also might have noticed that we wrote restrict: 'A', it means that we are going to use this directive as an attribute over html element. Why do we use scope.$watch !? well let's image a situation when somebody dynamically changes image url, then we will need to reevaluate our url and again display real image or image placeholder.
Here goes example of usage:
<img no-image ng-src="{product.imageUrl}}" />
To make code more flexible and reusable, we should get rid of hard coded image url inside the directive.
Since Angular JS is built with DI in mind let use that approach. Let's create some settings provider and inject it inside the directive. So our code will look like this:
app.factory('settings', function() { return { noImageUrl: "/demo/img/no_image_small.png" }; }); app.directive('noImage', function (settings) { var setDefaultImage = function (el) { el.attr('src', settings.noImageUrl); }; return { restrict: 'A', link: function (scope, el, attr) { scope.$watch(function() { return attr.ngSrc; }, function () { var src = attr.ngSrc; if (!src) { setDefaultImage(el); } }); el.bind('error', function() { setDefaultImage(el); }); } }; });
Now in case we need such directive in other application we easily can include script to the application and define a factory that will return appropriate url.
Good job! That source code woked perfectly for me .
ReplyDeletethank you, really helpful
ReplyDeletethank you, it works perfect for me!
ReplyDelete감사합니다. 이거 보고 해결했습니다. 최고!
ReplyDelete